def get_iec_frequencies(use_alpha, temperature, use_maxwellian=False): electron_mass = PhysicalConstants.electron_mass deuterium_mass = 2.01410178 * UnitConversions.amu_to_kg deuterium_tritium_mass = 5.0064125184e-27 alpha_mass = 3.7273 * UnitConversions.amu_to_kg alpha_species = ChargedParticle(alpha_mass, 2 * PhysicalConstants.electron_charge) dt_species = ChargedParticle(deuterium_tritium_mass, 2 * PhysicalConstants.electron_charge) background_species = ChargedParticle(electron_mass, -PhysicalConstants.electron_charge) n_background = 1e20 dt_velocity = np.sqrt(2 * 50e3 * PhysicalConstants.electron_charge / dt_species.m) alpha_velocity = np.sqrt(2 * 3.5e6 * PhysicalConstants.electron_charge / alpha_species.m) beam_species = alpha_species if use_alpha else dt_species beam_velocity = alpha_velocity if use_alpha else dt_velocity collision = CoulombCollision(beam_species, background_species, 1.0, beam_velocity) relaxation_process = MaxwellianRelaxationProcess(collision) if use_maxwellian: v_K = relaxation_process.numerical_kinetic_loss_maxwellian_frequency(n_background, temperature, beam_velocity) v_P = relaxation_process.numerical_kinetic_loss_maxwellian_frequency(n_background, temperature, beam_velocity) return v_K, v_P # maxwellian_frequency = relaxation_process.maxwellian_collisional_frequency(n_background, temperature, beam_velocity) # return maxwellian_frequency, maxwellian_frequency else: stationary_kinetic_frequency = relaxation_process.kinetic_loss_stationary_frequency(n_background, temperature, beam_velocity) stationary_momentum_frequency = relaxation_process.momentum_loss_stationary_frequency(n_background, temperature, beam_velocity) return stationary_kinetic_frequency, stationary_momentum_frequency
def get_maxwellian_collisional_frequencies(): # Generate charged particles alpha = ChargedParticle(6.64424e-27, PhysicalConstants.electron_charge * 2) electron = ChargedParticle(9.014e-31, -PhysicalConstants.electron_charge) impact_parameter_ratio = 1.0 beam_velocity = np.sqrt(2 * 3.5e6 * PhysicalConstants.electron_charge / alpha.m) n = 1e20 temperature = 20e3 * UnitConversions.eV_to_K print(n, temperature, beam_velocity) # Get reactant collision frequency and energy loss rate collision = CoulombCollision(electron, alpha, impact_parameter_ratio, beam_velocity) relaxation = MaxwellianRelaxationProcess(collision) numerical_v_P = relaxation.momentum_loss_maxwellian_frequency( n, temperature, beam_velocity) theoretical_v_P = 2 / (3 * np.sqrt( 2 * np.pi)) * relaxation.momentum_loss_stationary_frequency( n, temperature, beam_velocity) print("Numerical Momentum Loss Frequency: {}".format(numerical_v_P)) print("Theoretical Momentum Loss Frequency: {}".format(theoretical_v_P)) print("Numerical to theoretical momentum loss ratio: {}\n".format( numerical_v_P / theoretical_v_P)) numerical_v_K = relaxation.kinetic_loss_maxwellian_frequency( n, temperature, beam_velocity) print("Numerical Kinetic Loss Frequency: {}".format(numerical_v_K)) print("Numerical kinetic to momentum loss ratio: {}".format(numerical_v_K / numerical_v_P))
def plot_collisional_frequencies(): """ Plot the variation of collisional frequency with density, and temperature for different collisions """ deuterium = ChargedParticle(2.01410178 * 1.66054e-27, PhysicalConstants.electron_charge) electron = ChargedParticle(9.014e-31, -PhysicalConstants.electron_charge) impact_parameter_ratio = 1.0 # Is not necessary for this analysis velocities = np.logspace(4, 6, 100) number_density = np.logspace(20, 30, 100) VEL, N = np.meshgrid(velocities, number_density, indexing='ij') collision_pairs = [["Deuterium-Deuterium", deuterium, deuterium], ["Electron-Deuterium", electron, deuterium], ["Electron-Electron", electron, electron]] num_temp = 3 temperatures = np.logspace(3, 5, num_temp) * UnitConversions.eV_to_K for j, pair in enumerate(collision_pairs): name = pair[0] p_1 = pair[1] p_2 = pair[2] fig, ax = plt.subplots(2, num_temp, figsize=(15, 20)) fig.suptitle("Collisional Frequencies for {} Relaxation".format(name)) for i, temp in enumerate(temperatures): collision = CoulombCollision(p_1, p_2, impact_parameter_ratio, VEL) relaxation_process = RelaxationProcess(collision) kinetic_frequency = relaxation_process.kinetic_loss_stationary_frequency( N, temp, VEL) momentum_frequency = relaxation_process.momentum_loss_stationary_frequency( N, temp, VEL, first_background=True) im = ax[0, i].contourf(np.log10(VEL), np.log10(N), np.log10(kinetic_frequency), 100) ax[0, i].set_title("Kinetic: T = {}".format( temp * UnitConversions.K_to_eV)) ax[0, i].set_xlabel("Velocity (ms-1)") ax[0, i].set_ylabel("Number density (m-3)") fig.colorbar(im, ax=ax[0, i]) im = ax[1, i].contourf(np.log10(VEL), np.log10(N), np.log10(momentum_frequency), 100) ax[1, i].set_title("Momentum: T = {}".format( temp * UnitConversions.K_to_eV)) ax[1, i].set_xlabel("Velocity (ms-1)") ax[1, i].set_ylabel("Number density (m-3)") fig.colorbar(im, ax=ax[1, i]) plt.show()
def compare_maxwellian_and_stationary_frequencies_against_temperature( number_density, temperature): # Generate charged particles alpha = ChargedParticle(6.64424e-27, PhysicalConstants.electron_charge * 2) electron = ChargedParticle(9.014e-31, -PhysicalConstants.electron_charge) # Get deuterium and alpha velocities from product and reactant beam # energies e_alpha = 1e3 * PhysicalConstants.electron_charge v_alpha = np.sqrt(2 * e_alpha / alpha.m) # Get product collision frequency and energy loss rate impact_parameter_ratio = 1.0 # Is not necessary for this analysis product_collision = CoulombCollision(alpha, electron, impact_parameter_ratio, v_alpha) product_relaxation = MaxwellianRelaxationProcess(product_collision) alpha_stationary_momentum_frequency = product_relaxation.momentum_loss_stationary_frequency( number_density, temperature, v_alpha) maxwellian_frequency = np.zeros(temperature.shape) alpha_maxwellian_momentum_frequency = np.zeros(temperature.shape) for i, T in enumerate(temperature): # Get maxwellian frequencies v = product_relaxation.maxwellian_collisional_frequency( number_density, T, v_alpha) maxwellian_frequency[i] = v v_P = product_relaxation.numerical_momentum_loss_maxwellian_frequency( number_density, T, v_alpha, epsrel=1e-8) alpha_maxwellian_momentum_frequency[i] = v_P print(T, v_P, v_P - alpha_stationary_momentum_frequency[i], np.abs(v_P - v) / v) fig, ax = plt.subplots() temperature *= UnitConversions.K_to_eV ax.loglog(temperature, alpha_stationary_momentum_frequency, label="Stationary") ax.loglog(temperature, maxwellian_frequency, label="Maxwellian") ax.loglog(temperature, alpha_maxwellian_momentum_frequency, label="Numerical Maxwellian") ax.legend() plt.show()
def run_sim(sim_type): """ Runs simulation of a single species in a uniform velocity distribution sim_type: Instances of simulation class for coulomb collisions """ # Set seed # for seed in range(5): seed = 1 np.random.seed(seed) # Set up simulation particle = ChargedParticle(2.01410178 * 1.66054e-27, PhysicalConstants.electron_charge) n = 10000 weight = 1 weighted_particle = ChargedParticle( 2.01410178 * 1.66054e-27 * weight, PhysicalConstants.electron_charge * weight) sim = sim_type(n, particle, weight) # Get initial uniform velocities v_max = 1.0 velocities = np.random.uniform(-v_max, v_max, size=(n, 3)) # Get time step from collisional frequencies c = CoulombCollision(weighted_particle, weighted_particle, 1.0, 2.0 * v_max) process = RelaxationProcess(c) v_K = process.kinetic_loss_stationary_frequency(n * weight, 1.0, 2.0 * v_max) v_P = process.momentum_loss_stationary_frequency(n * weight, 1.0, 2.0 * v_max) collision_time = 1.0 / max([v_K, v_P]) dt = 0.1 * collision_time final_time = 2.5 * collision_time t, v_results = sim.run_sim(velocities, dt, final_time) post_process_results(t, v_results)
def plot_impact_parameter_variation_with_temperature(): """ Simple function to see how the impact parameter varies with temperature """ temperatures = UnitConversions.eV_to_K * np.logspace(1, 4, 100) deuterium = ChargedParticle(2.01410178 * 1.66054e-27, PhysicalConstants.electron_charge) electron = ChargedParticle(9.014e-31, -PhysicalConstants.electron_charge) electron_b_90 = np.zeros(temperatures.shape) deuterium_b_90 = np.zeros(temperatures.shape) for i, temperature in enumerate(temperatures): electron_velocity = np.sqrt(PhysicalConstants.boltzmann_constant * temperature / electron.m) deuterium_velocity = np.sqrt(PhysicalConstants.boltzmann_constant * temperature / deuterium.m) collision = CoulombCollision(deuterium, electron, 1.0, electron_velocity) b_90 = collision.b_90 electron_b_90[i] = b_90 collision = CoulombCollision(deuterium, electron, 1.0, deuterium_velocity) b_90 = collision.b_90 deuterium_b_90[i] = b_90 plt.figure() plt.loglog(temperatures, electron_b_90, label="Electron Velocity") plt.loglog(temperatures, deuterium_b_90, label="Deuterium Velocity") plt.title( "Impact Parameter vs Temperature for Deuterium-Electron binary collision" ) plt.ylabel("Impact Parameter (m)") plt.xlabel("Temperature (K)") plt.legend() plt.show()
def run_electon_beam_into_argon_gas_sim(sim_type): """ Run a simulation of an electron beam into a background gas at a given temperature """ p_1 = ChargedParticle(PhysicalConstants.electron_mass, -PhysicalConstants.electron_charge) p_2 = ChargedParticle(PhysicalConstants.electron_mass, -PhysicalConstants.electron_charge) p_3 = ChargedParticle(39.948 * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge) w_1 = 1 w_2 = 1000 w_3 = 1000 n = int(1e3) if sim_type == "Nanbu": sim = NanbuCollisionModel(np.asarray([n, n, n]), np.asarray([p_1, p_2, p_3]), np.asarray([w_1, w_2, w_3]), coulomb_logarithm=10.0, frozen_species=np.asarray( [False, True, True])) else: raise ValueError("Invalid sim type") # Set initial velocity conditions of beam beam_velocity = np.sqrt(2 * 100 * PhysicalConstants.electron_charge / p_1.m) velocities = np.zeros((3 * n, 3)) velocities[:n, :] = np.asarray([0.0, 0.0, beam_velocity]) # Set initial velocity conditions of background k_T = 2.0 sigma = np.sqrt(2 * k_T * PhysicalConstants.electron_charge / p_2.m) electron_velocities = np.random.normal( loc=0.0, scale=sigma, size=velocities[n:2 * n, :].shape) / np.sqrt(3) velocities[n:2 * n, :] = electron_velocities k_T = 0.02 sigma = np.sqrt(2 * k_T * PhysicalConstants.electron_charge / p_3.m) ion_velocities = np.random.normal( loc=0.0, scale=sigma, size=velocities[2 * n:, :].shape) / np.sqrt(3) velocities[2 * n:, :] = ion_velocities tau = get_relaxation_time(p_1, n * w_2, beam_velocity) dt = 0.01 * tau final_time = 1.0 * tau t, v_results = sim.run_sim(velocities, dt, final_time) t /= tau v_z_estimate = 1.0 - 3 * t v_ort_estimate = 3.98 * t fig, ax = plt.subplots(2, figsize=(10, 10)) l_v_z = ax[0].plot(t, np.mean(v_results[:n, 2, :], axis=0) / beam_velocity, color="b", label="<v_z>") l_v_z_estimate = ax[0].plot(t, v_z_estimate, linestyle="--", color="c", label="<v_z_estimate>") ax[0].set_xlim([0.0, t[-1]]) ax[0].set_xlabel("Timestep") ax[0].set_ylabel("Velocities ms-1") ax[0].set_ylim([0.0, 1.0]) ax[0].set_title("Beam Velocities") # Twin axes for second plot to replicate paper set up ax_twin = ax[0].twinx() l_ort = ax_twin.plot( t, np.mean(v_results[:n, 0, :]**2 + v_results[:n, 1, :]**2, axis=0) / beam_velocity**2, color="r", label="<v_ort^2>") l_ort_estimate = ax_twin.plot(t, v_ort_estimate, linestyle="--", color="y", label="<v_ort_estimate>") ax_twin.set_ylabel("Square of Velocities (ms-1)^2") ax_twin.set_ylim([0.0, 0.33]) lines = l_v_z + l_v_z_estimate + l_ort + l_ort_estimate labs = [l.get_label() for l in lines] ax[0].legend(lines, labs) ax[1].plot(t, np.mean(v_results[2 * n:, 0, :], axis=0), label="<v_x>") ax[1].plot(t, np.mean(v_results[2 * n:, 1, :], axis=0), label="<v_y>") ax[1].plot(t, np.mean(v_results[2 * n:, 2, :], axis=0), label="<v_z>") ax[1].set_xlim([0.0, t[-1]]) ax[1].legend() ax[1].set_xlabel("Timestep") ax[1].set_ylabel("Velocities ms-1") ax[1].set_title("Background Velocities") plt.show() energy = p_1.m * np.sum(v_results[:n, 0, :]**2 + v_results[:n, 1, :]**2 + v_results[:n, 2, :]**2, axis=0) energy += p_2.m * np.sum(v_results[n:, 0, :]**2 + v_results[n:, 1, :]**2 + v_results[n:, 2, :]**2, axis=0) x_mom = p_1.m * np.sum(v_results[:n, 0, :], axis=0) + p_2.m * np.sum( v_results[n:, 0, :], axis=0) y_mom = p_1.m * np.sum(v_results[:n, 1, :], axis=0) + p_1.m * np.sum( v_results[n:, 1, :], axis=0) z_mom = p_1.m * np.sum(v_results[:n, 2, :], axis=0) + p_1.m * np.sum( v_results[n:, 2, :], axis=0) fig, ax = plt.subplots(2) ax[0].plot(t, energy) ax[0].set_title("Conservation of Energy") ax[1].plot(t, x_mom, label="x momentum") ax[1].plot(t, y_mom, label="y momentum") ax[1].plot(t, z_mom, label="z momentum") ax[1].legend() ax[1].set_title("Conservation of Momentum") fig.suptitle("Conservation Plots") plt.show() return t, v_results
def generate_sim_results(number_densities, T): # Set simulation independent parameters N = int(1e4) dt_factor = 0.01 w_1 = int(1) # Generate result lists energy_results = dict() velocity_results = dict() t_halves = dict() t_theory = dict() # Run simulations names = ["reactant", "product"] for j, name in enumerate(names): t_halves[name] = np.zeros(number_densities.shape) t_theory[name] = np.zeros(number_densities.shape) energy_results[name] = [] velocity_results[name] = [] for i, n in enumerate(number_densities): # Set up beam sim if "product" == name: p_1 = ChargedParticle(6.64424e-27, 2 * PhysicalConstants.electron_charge) energy = 3.5e6 * PhysicalConstants.electron_charge elif "reactant" == name: p_1 = ChargedParticle(2.014102 * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge) energy = 50e3 * PhysicalConstants.electron_charge else: raise ValueError() beam_velocity = np.sqrt(2 * energy / p_1.m) p_2 = ChargedParticle(2.014102 * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge) # Instantiate simulation w_2 = int(n / N) sim = AbeCoulombCollisionModel(N, p_1, w_1=w_1, N_2=N, particle_2=p_2, w_2=w_2, freeze_species_2=False) # Set up velocities velocities = np.zeros((2 * N, 3)) velocities[:N, :] = np.asarray([0.0, 0.0, beam_velocity]) velocities[N:] = np.asarray([0.0, 0.0, 0.0]) # Get approximate time scale impact_parameter_ratio = 1.0 # Is not necessary for this analysis reactant_collision = CoulombCollision(p_1, p_2, impact_parameter_ratio, beam_velocity) reactant_relaxation = RelaxationProcess(reactant_collision) deuterium_kinetic_frequency = reactant_relaxation.kinetic_loss_stationary_frequency(N * w_2, T, beam_velocity) tau = 1.0 / deuterium_kinetic_frequency dt = dt_factor * tau final_time = 4.0 * tau t, v_results = sim.run_sim(velocities, dt, final_time) # Get salient results velocities = np.mean(np.sqrt(v_results[:N, 0, :] ** 2 + v_results[:N, 1, :] ** 2 + v_results[:N, 2, :] ** 2), axis=0) energies = 0.5 * p_1.m * velocities ** 2 velocity_results[name].append(velocities) energy_results[name].append(velocities) # Get energy half times energy_time_interpolator = interp1d(energies / energy, t) t_half = energy_time_interpolator(0.5) t_halves[name][i] = t_half t_theory[name][i] = tau # Save results np.savetxt("stationary_{}_{}_{}_half_times".format(name, N, dt_factor), t_halves[name], fmt='%s') np.savetxt("stationary_{}_{}_{}_velocities".format(name, N, dt_factor), velocity_results[name], fmt='%s') np.savetxt("stationary_{}_{}_{}_energies".format(name, N, dt_factor), energy_results[name], fmt='%s') # Plot results plt.figure() for name in names: plt.loglog(number_densities, t_halves[name], label="{}_Simulation".format(name)) plt.loglog(number_densities, t_theory[name], label="{}_Theoretical values".format(name)) plt.title("Comparison of thermalisation rates of products and reactants") plt.legend() plt.savefig("energy_half_times") plt.show()
def run_sim(): p_1 = ChargedParticle(PhysicalConstants.electron_mass, -PhysicalConstants.electron_charge) p_2 = ChargedParticle(39.948 * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge) n = int(1e4) w = int(1e17) sim = NanbuCollisionModel(np.asarray([n, n]), np.asarray([p_1, p_2]), np.asarray([w, w]), coulomb_logarithm=15.9, frozen_species=np.asarray([False, True])) # Set initial velocity conditions of background velocities = np.zeros((2 * n, 3)) k_T = 1e3 sigma = np.sqrt(2 * k_T * PhysicalConstants.electron_charge / p_1.m) electron_velocities = np.random.normal( loc=0.0, scale=sigma, size=velocities[:n, :].shape) / np.sqrt(3) velocities[:n, :] = electron_velocities sigma = np.sqrt(2 * k_T * PhysicalConstants.electron_charge / p_2.m) ion_velocities = np.random.normal( loc=0.0, scale=sigma, size=velocities[:n, :].shape) / np.sqrt(3) velocities[n:, :] = ion_velocities V = np.sqrt(8.0 * k_T / (np.pi * p_1.m)) num_periods = 50.0 frequencies = [1e4, 1e5] v_squared_results = [] for i, f in enumerate(frequencies): t_p = 1 / f omega = 2 * np.pi * f dt = t_p / 10.0 dt = min(dt, 2.5e-7) t = 0.0 n = 1 times = [t] v_squared_results.append([0.0]) I = np.zeros((n, 3)) I[:, :] = np.asarray([1.0, 0.0, 0.0]) new_vel = np.copy(velocities) while t < num_periods * t_p: I_results = [] v_means = [] while t < n * t_p: print(t / t_p) # Accelerate particles due to field new_vel[:n, :] -= I * V * np.sin(omega * t) # Carry out coulomb collisions new_vel = sim.single_time_step(new_vel, dt) # Calculate I v_mean = np.mean( np.sqrt(new_vel[:n, 0]**2 + new_vel[:n, 1]**2 + new_vel[:n, 2]**2)) current = v_mean**2 * dt v_means.append(v_mean) I_results.append(current) # Correct velocities new_vel[:n, :] += I * V * np.sin(omega * t) t += dt # plt.figure() # plt.plot(v_means) # plt.show() v = np.sum(np.asarray(I_results)) / t_p v_squared_results[i].append(v / V**2) n += 1 # Plot results plt.figure() for v_squared in v_squared_results: plt.plot(np.asarray(v_squared)**2) plt.scatter(range(len(v_squared)), np.asarray(v_squared)**2) plt.show() # Save results v_squared_results = np.asarray(v_squared_results) np.savetxt("v_squared_results", v_squared_results)
def compare_product_and_reactant_energy_loss_rates( number_density, reaction_name, plot_results=True, plot_energy_frequencies=True): # Generate charged particles alpha = ChargedParticle(6.64424e-27, PhysicalConstants.electron_charge * 2) electron = ChargedParticle(9.014e-31, -PhysicalConstants.electron_charge) if reaction_name == "DT": reactant = ChargedParticle(5.0064125184e-27, 2 * PhysicalConstants.electron_charge) e_alpha = 3.5e6 * PhysicalConstants.electron_charge e_reactant = 30e3 * PhysicalConstants.electron_charge Z = 2 elif reaction_name == "pB": reactant = ChargedParticle((10.81 + 1) * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge * 12) e_alpha = 8.6e6 / 3 * PhysicalConstants.electron_charge e_reactant = 500e3 * PhysicalConstants.electron_charge Z = 6 else: raise ValueError("Name is invalid!") temperature = e_reactant # Get deuterium and alpha velocities from product and reactant beam # energies v_alpha = np.sqrt(2 * e_alpha / alpha.m) v_reactant = np.sqrt(2 * e_reactant / reactant.m) # Get reactant collision frequency and energy loss rate impact_parameter_ratio = 1.0 # Is not necessary for this analysis reactant_collision = CoulombCollision(reactant, reactant, impact_parameter_ratio, v_reactant) reactant_relaxation = RelaxationProcess(reactant_collision) deuterium_kinetic_frequency = reactant_relaxation.kinetic_loss_stationary_frequency( Z * number_density, temperature, v_reactant) deuterium_energy_loss_rates = reactant_relaxation.energy_loss_with_distance( Z * number_density, temperature, v_reactant) deuterium_momentum_frequency = reactant_relaxation.momentum_loss_stationary_frequency( Z * number_density, temperature, v_reactant) deuterium_momentum_loss_rates = deuterium_momentum_frequency * reactant.m # Get product collision frequency and energy loss rate product_collision = CoulombCollision(alpha, reactant, impact_parameter_ratio, v_alpha) product_relaxation = RelaxationProcess(product_collision) alpha_kinetic_frequency = product_relaxation.kinetic_loss_stationary_frequency( Z * number_density, temperature, v_alpha) alpha_energy_loss_rates = product_relaxation.energy_loss_with_distance( Z * number_density, temperature, v_alpha) alpha_momentum_frequency = product_relaxation.momentum_loss_stationary_frequency( Z * number_density, temperature, v_alpha) alpha_momentum_loss_rates = alpha_momentum_frequency * alpha.m # Compare velocities of particles v_electron = np.sqrt(2 * PhysicalConstants.boltzmann_constant * temperature / electron.m) print( "For stationary collisions to be a reasonable approximation, the beams need " "to be travelling faster than the electron thermal velocity... ") print("Electron Normalised Velocity: {}".format(v_electron / 3e8)) print("Alpha-Electron Velocity Ratio: {}".format(v_alpha / v_electron)) print("Deuterium-Electron Velocity Ratio: {}".format(v_reactant / v_electron)) print(deuterium_energy_loss_rates * e_alpha / alpha_energy_loss_rates / e_reactant) deuterium_energy_distance_travelled = e_reactant / deuterium_energy_loss_rates alpha_energy_distance_travelled = e_alpha / alpha_energy_loss_rates if plot_results: fig, ax = plt.subplots(2, 2, figsize=(7, 7)) if plot_energy_frequencies: # energy results ax[0, 0].loglog(number_density, deuterium_kinetic_frequency, label="Reactant Beam") ax[0, 0].loglog(number_density, alpha_kinetic_frequency, label="Product Beam") ax[0, 0].set_xlabel("Number density (m-3)") ax[0, 0].set_ylabel("Energy Collision Frequency (s-1)") ax[0, 0].set_title( "Energy Collision Frequencies - {}".format(reactant_name)) ax[0, 0].legend() ax[1, 0].loglog(number_density, deuterium_energy_loss_rates, label="Reactant Beam") ax[1, 0].loglog(number_density, alpha_energy_loss_rates, label="Product Beam") ax[1, 0].set_xlabel("Number density (m-3)") ax[1, 0].set_ylabel("Energy Loss Rate per metre (Jm-1)") ax[1, 0].set_title( "Energy Loss Rates per metre - {}".format(reactant_name)) ax[1, 0].legend() ax[0, 1].loglog(number_density, deuterium_energy_loss_rates / e_reactant, label="Reactant Beam") ax[0, 1].loglog(number_density, alpha_energy_loss_rates / e_alpha, label="Product Beam") ax[0, 1].axhline(0.5, linestyle="--") ax[0, 1].set_xlabel("Number density (m-3)") ax[0, 1].set_ylabel("Normalised Kinetic Loss Rate (m-1)") ax[0, 1].set_title( "Normalised Kinetic Loss Rate - {}".format(reactant_name)) ax[0, 1].legend() ax[1, 1].loglog(number_density, deuterium_energy_distance_travelled, label="Reactant Beam") ax[1, 1].loglog(number_density, alpha_energy_distance_travelled, label="Product Beam") ax[1, 1].set_xlabel("Number density (m-3)") ax[1, 1].set_ylabel("Distance traveller (m)") ax[1, 1].set_title("Distance travelled - {}".format(reactant_name)) ax[1, 1].legend() else: # momentum results ax[0, 0].loglog(number_density, deuterium_momentum_frequency, label="Reactant Beam") ax[0, 0].loglog(number_density, alpha_momentum_frequency, label="Product Beam") ax[0, 0].set_xlabel("Number density (m-3)") ax[0, 0].set_ylabel("Momentum Collision Frequency (s-1)") ax[0, 0].set_title( "Momentum Collision Frequencies - {}".format(reactant_name)) ax[0, 0].legend() ax[1, 0].loglog(number_density, deuterium_momentum_loss_rates, label="Reactant Beam") ax[1, 0].loglog(number_density, alpha_momentum_loss_rates, label="Product Beam") ax[1, 0].set_xlabel("Number density (m-3)") ax[1, 0].set_ylabel("Momentum Loss Rate per metre (Jm-1)") ax[1, 0].set_title( "Momentum Loss Rates per metre - {}".format(reactant_name)) ax[1, 0].legend() deuterium_momentum = reactant.m * v_reactant alpha_momentum = alpha.m * v_alpha ax[0, 1].loglog(number_density, deuterium_momentum_loss_rates / deuterium_momentum, label="Reactant Beam") ax[0, 1].loglog(number_density, alpha_momentum_loss_rates / alpha_momentum, label="Product Beam") ax[0, 1].axhline(0.1, linestyle="--") ax[0, 1].set_xlabel("Number density (m-3)") ax[0, 1].set_ylabel("Normalised Momentum Loss Rate (m-1)") ax[0, 1].set_title( "Normalised Momentum Loss Rate - {}".format(reactant_name)) ax[0, 1].legend() ax[1, 1].loglog(number_density, deuterium_momentum / deuterium_momentum_loss_rates, label="Reactant Beam") ax[1, 1].loglog(number_density, alpha_momentum / alpha_momentum_loss_rates, label="Product Beam") ax[1, 1].set_xlabel("Number density (m-3)") ax[1, 1].set_ylabel("Distance traveller (m)") ax[1, 1].set_title("Distance travelled - {}".format(reactant_name)) ax[1, 1].legend() # fig.suptitle("Comparison of product and reactant beam loss rates due to electron collisions") fig.tight_layout() plt.savefig("relaxation_rates_{}".format(reactant_name)) plt.show() return deuterium_energy_distance_travelled, alpha_energy_distance_travelled
def generate_sim_results(number_densities, T): # Set simulation independent parameters N = int(1e3) dt_factor = 0.01 w_1 = int(1) # Make results directory if it does not exist res_dir = "results" if not os.path.exists(res_dir): os.mkdir(res_dir) # Generate result lists energy_results = dict() velocity_results = dict() t_halves = dict() t_theory = dict() # Run simulations names = ["reactant", "product"] for j, name in enumerate(names): t_halves[name] = np.zeros(number_densities.shape) t_theory[name] = np.zeros(number_densities.shape) energy_results[name] = [] velocity_results[name] = [] for i, n in enumerate(number_densities): print("Number density: {}".format(n)) # Set up beam sim if "product" == name: p_1 = ChargedParticle(6.64424e-27, 2 * PhysicalConstants.electron_charge) energy = 3.5e6 * PhysicalConstants.electron_charge elif "reactant" == name: p_1 = ChargedParticle(2.014102 * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge) energy = 50e3 * PhysicalConstants.electron_charge else: raise ValueError() beam_velocity = np.sqrt(2 * energy / p_1.m) p_2 = ChargedParticle(2.014102 * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge) # Instantiate simulation w_2 = int(n / N) particle_numbers = np.asarray([N, N]) sim = NanbuCollisionModel(particle_numbers, np.asarray([p_1, p_2]), np.asarray([w_1, w_2]), coulomb_logarithm=10.0, frozen_species=np.asarray([False, False])) # Set up velocities velocities = np.zeros((2 * N, 3)) velocities[:N, :] = np.asarray([0.0, 0.0, beam_velocity]) k_T = T * PhysicalConstants.boltzmann_constant sigma = np.sqrt(2 * k_T / p_2.m) background_velocities = np.random.normal( loc=0.0, scale=sigma, size=velocities[N:2 * N, :].shape) / np.sqrt(3) velocities[N:2 * N, :] = background_velocities # Get approximate time scale impact_parameter_ratio = 1.0 # Is not necessary for this analysis reactant_collision = CoulombCollision(p_1, p_2, impact_parameter_ratio, beam_velocity) reactant_relaxation = MaxwellianRelaxationProcess( reactant_collision) deuterium_kinetic_frequency = reactant_relaxation.numerical_kinetic_loss_maxwellian_frequency( N * w_2, T, beam_velocity) tau = 1.0 / deuterium_kinetic_frequency dt = dt_factor * tau final_time = 4.0 * tau t, v_results = sim.run_sim(velocities, dt, final_time) # Get salient results velocities = np.mean( np.sqrt(v_results[:N, 0, :]**2 + v_results[:N, 1, :]**2 + v_results[:N, 2, :]**2), axis=0) energies = 0.5 * p_1.m * velocities**2 velocity_results[name].append(velocities) energy_results[name].append(energies) velocities_deuterium = np.mean( np.sqrt(v_results[N:2 * N, 0, :]**2 + v_results[N:2 * N, 1, :]**2 + v_results[N:2 * N, 2, :]**2), axis=0) energies_deuterium = 0.5 * p_2.m * np.mean( np.sqrt(v_results[N:2 * N, 0, :]**2 + v_results[N:2 * N, 1, :]**2 + v_results[N:2 * N, 2, :]**2), axis=0) # Plot results fig, ax = plt.subplots(2) ax[0].plot(t, velocities_deuterium, label="ions") ax[0].plot(t, velocities, label="beam") ax[1].plot(t, energies_deuterium, label="ions") ax[1].plot(t, energies, label="beam") ax[1].plot(t, energies + energies_deuterium, label="total") plt.legend() plt.savefig( os.path.join( res_dir, "stationary_{}_{}_{}_{}.png".format(name, N, dt_factor, n))) # Get energy half times energy_time_interpolator = interp1d(energies / energy, t) t_half = energy_time_interpolator(0.5) t_halves[name][i] = t_half t_theory[name][i] = tau # Save results np.savetxt(os.path.join( res_dir, "stationary_{}_{}_{}_half_times".format(name, N, dt_factor)), t_halves[name], fmt='%s') np.savetxt(os.path.join( res_dir, "stationary_{}_{}_{}_velocities".format(name, N, dt_factor)), velocity_results[name], fmt='%s') np.savetxt(os.path.join( res_dir, "stationary_{}_{}_{}_energies".format(name, N, dt_factor)), energy_results[name], fmt='%s') # Plot results plt.figure() for name in names: plt.loglog(number_densities, t_halves[name], label="{}_Simulation".format(name)) plt.loglog(number_densities, t_theory[name], label="{}_Theoretical values".format(name)) plt.title("Comparison of thermalisation rates of products and reactants") plt.legend() plt.savefig(os.path.join(res_dir, "energy_half_times")) plt.show()
def run_electron_thermal_relaxation_sim(sim_type): """ Run a simulation of single species electron thermal relaxation """ # Set seed np.random.seed(1) p_1 = ChargedParticle(PhysicalConstants.electron_mass, -PhysicalConstants.electron_charge) n = int(1e4) sim = sim_type(n, p_1, 1, coulomb_logarithm=10.0) T_y = 11500.0 T_factor = 1.3 T_e = (T_factor * T_y + 2 * T_y) / 3.0 velocities = np.zeros((n, 3)) sigma_ort = np.sqrt(PhysicalConstants.boltzmann_constant * T_y / p_1.m) sigma_x = np.sqrt(PhysicalConstants.boltzmann_constant * T_factor * T_y / p_1.m) velocities[:, 0] = np.random.normal(loc=0.0, scale=sigma_x, size=velocities[:, 0].shape) velocities[:, 1] = np.random.normal(loc=0.0, scale=sigma_ort, size=velocities[:, 1].shape) velocities[:, 2] = np.random.normal(loc=0.0, scale=sigma_ort, size=velocities[:, 2].shape) # Get simulation time tau = get_relaxation_time(p_1, n, T_e) dt = 0.02 * tau final_time = 12.0 * tau T_x = np.std(velocities[:, 0], axis=0) ** 2 * p_1.m / (PhysicalConstants.boltzmann_constant) T_y = np.std(velocities[:, 1], axis=0) ** 2 * p_1.m / (PhysicalConstants.boltzmann_constant) T_z = np.std(velocities[:, 2], axis=0) ** 2 * p_1.m / (PhysicalConstants.boltzmann_constant) t, v_results = sim.run_sim(velocities, dt, final_time) t /= tau dT_0 = (T_factor - 1.0) * T_y dT = dT_0 * np.exp(-8.0 * t / (5.0 * np.sqrt(2.0 * np.pi))) T_x_theory = 1.0 + dT / T_e * 2.0 / 3.0 T_ort_theory = 1.0 - dT / T_e / 3.0 fig, ax = plt.subplots(1, figsize=(10, 10)) T_x = np.std(v_results[:, 0, :], axis=0) ** 2 * p_1.m / (PhysicalConstants.boltzmann_constant) T_y = np.std(v_results[:, 1, :], axis=0) ** 2 * p_1.m / (PhysicalConstants.boltzmann_constant) T_z = np.std(v_results[:, 2, :], axis=0) ** 2 * p_1.m / (PhysicalConstants.boltzmann_constant) ax.plot(t, T_x / T_e, label="T_x") ax.plot(t, T_y / T_e, label="T_y") ax.plot(t, T_z / T_e, label="T_z") ax.plot(t, T_x_theory, label="T_x_num", linestyle="--") ax.plot(t, T_ort_theory, label="T_ort_num", linestyle="--") ax.set_xlim([0.0, t[-1]]) ax.legend() ax.set_xlabel("Timestep") ax.set_ylabel("Temperature") ax.set_title("Electron thermal relaxation") plt.savefig("electron_thermal_relaxation") plt.show() # Plot velocity histograms fig, ax = plt.subplots(2, 3, figsize=(10, 10)) ax[0, 0].hist(v_results[:, 0, 0], 100) ax[0, 1].hist(v_results[:, 1, 0], 100) ax[0, 2].hist(v_results[:, 2, 0], 100) ax[1, 0].hist(v_results[:, 0, -1], 100) ax[1, 1].hist(v_results[:, 1, -1], 100) ax[1, 2].hist(v_results[:, 2, -1], 100) plt.savefig("electron_thermal_relaxation_velocity_distributions") plt.show() # Plot energy conservation energy = np.sum(v_results[:, 0, :] ** 2 + v_results[:, 1, :] ** 2 + v_results[:, 2, :] ** 2, axis=0) x_mom = np.sum(v_results[:, 0, :], axis=0) y_mom = np.sum(v_results[:, 1, :], axis=0) z_mom = np.sum(v_results[:, 2, :], axis=0) fig, ax = plt.subplots(2) ax[0].plot(t, energy) ax[0].set_title("Conservation of Energy") ax[1].plot(t, x_mom, label="x momentum") ax[1].plot(t, y_mom, label="y momentum") ax[1].plot(t, z_mom, label="z momentum") ax[1].legend() ax[1].set_title("Conservation of Momentum") fig.suptitle("Conservation Plots") plt.show()
def generate_sim_results(number_densities, reactant_name, plot_individual_sims=False): # Set simulation independent parameters N = int(1e4) dt_factor = 0.01 # Generate result lists energy_results = dict() velocity_results = dict() t_halves = dict() t_theory = dict() # Make results directory if it does not exist res_dir = os.path.join("results", reactant_name) if not os.path.exists(res_dir): os.mkdir(res_dir) # Run simulations names = ["reactant", "product"] for j, name in enumerate(names): t_halves[name] = np.zeros(number_densities.shape) t_theory[name] = np.zeros(number_densities.shape) energy_results[name] = [] velocity_results[name] = [] for i, n in enumerate(number_densities): # Set temperature of background - this is assumed to be lower than the energy of the beam T_b = 1e3 if reactant_name is "DT" else 10e3 T_b *= UnitConversions.eV_to_K # Set up beam sim if "product" == name: p_1 = ChargedParticle(6.64424e-27, 2 * PhysicalConstants.electron_charge) if reactant_name == "DT": p_2 = ChargedParticle( 5.0064125184e-27, 2 * PhysicalConstants.electron_charge) energy = 3.5e6 * PhysicalConstants.electron_charge Z = 2 # Get number density of products from reactivity T = 50 reaction = DTReaction() reactivity_fit = BoschHaleReactivityFit(reaction) reactivity = reactivity_fit.get_reactivity(T) n_1 = n * reactivity elif reactant_name == "pB": p_2 = ChargedParticle( (10.81 + 1) * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge * 12) energy = 8.6e6 / 3.0 * PhysicalConstants.electron_charge Z = 6 # Get number density of products from reactivity T = 500 reaction = pBReaction() reactivity_fit = BoschHaleReactivityFit(reaction) reactivity = reactivity_fit.get_reactivity(T) n_1 = n * reactivity else: raise ValueError("Invalid reactant") elif "reactant" == name: n_1 = n if reactant_name == "DT": p_1 = ChargedParticle( 5.0064125184e-27, 2 * PhysicalConstants.electron_charge) p_2 = ChargedParticle( 5.0064125184e-27, 2 * PhysicalConstants.electron_charge) energy = 50e3 * PhysicalConstants.electron_charge Z = 2 elif reactant_name == "pB": p_1 = ChargedParticle( (10.81 + 1) * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge * 12) p_2 = ChargedParticle( (10.81 + 1) * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge * 12) energy = 500e3 * PhysicalConstants.electron_charge Z = 6 else: raise ValueError("Invalid reactant") else: raise ValueError() beam_velocity = np.sqrt(2 * energy / p_1.m) electron = ChargedParticle(9.014e-31, -PhysicalConstants.electron_charge) # Instantiate simulation w_b = long(n / N) w_1 = long(n_1 / N) w_1 = w_1 if w_1 > 0 else long(1) print(w_1, w_b) particle_numbers = np.asarray([N, N, N]) sim = NanbuCollisionModel(particle_numbers, np.asarray([p_1, p_2, electron]), np.asarray([w_1, w_b, Z * w_b]), coulomb_logarithm=10.0, frozen_species=np.asarray( [False, False, False]), include_self_collisions=True) # Set up velocities velocities = np.zeros((np.sum(particle_numbers), 3)) velocities[:N, :] = np.asarray([0.0, 0.0, beam_velocity]) # Small maxwellian distribution used for background species k_T = T_b * PhysicalConstants.boltzmann_constant sigma = np.sqrt(2 * k_T / p_2.m) p_2_velocities = np.random.normal( loc=0.0, scale=sigma, size=velocities[N:2 * N, :].shape) / np.sqrt(3) velocities[N:2 * N, :] = p_2_velocities sigma = np.sqrt(2 * k_T / electron.m) electron_velocities = np.random.normal( loc=0.0, scale=sigma, size=velocities[2 * N:, :].shape) / np.sqrt(3) velocities[2 * N:, :] = electron_velocities # Get approximate time scale impact_parameter_ratio = 1.0 # Is not necessary for this analysis tau = sys.float_info.max for background_particle in [p_2]: reactant_collision = CoulombCollision(p_1, background_particle, impact_parameter_ratio, beam_velocity) relaxation = MaxwellianRelaxationProcess(reactant_collision) kinetic_frequency = relaxation.numerical_kinetic_loss_maxwellian_frequency( N * w_b, T_b, beam_velocity) tau = min(tau, 1.0 / kinetic_frequency) dt = dt_factor * tau final_time = 4.0 * tau t, v_results = sim.run_sim(velocities, dt, final_time) # Get salient results velocities = np.mean( np.sqrt(v_results[:N, 0, :]**2 + v_results[:N, 1, :]**2 + v_results[:N, 2, :]**2), axis=0) energies = 0.5 * p_1.m * np.mean( v_results[:N, 0, :]**2 + v_results[:N, 1, :]**2 + v_results[:N, 2, :]**2, axis=0) total_energies = 0.5 * p_1.m * np.sum( v_results[:N, 0, :]**2 + v_results[:N, 1, :]**2 + v_results[:N, 2, :]**2, axis=0) total_energy = N * w_1 * energy assert np.isclose(energies[0], energy, atol=0.01 * energy), "{} != {}".format( energies[0], w_1 * N * energy) assert np.isclose(total_energies[0] * w_1, total_energy, atol=0.01 * total_energy), "{} != {}".format( total_energies[0], total_energy) velocity_results[name].append(velocities) energy_results[name].append(energies) velocities_p_2 = np.mean(np.sqrt(v_results[N:2 * N, 0, :]**2 + v_results[N:2 * N, 1, :]**2 + v_results[N:2 * N, 2, :]**2), axis=0) energies_p_2 = 0.5 * p_2.m * np.mean( v_results[N:2 * N, 0, :]**2 + v_results[N:2 * N, 1, :]**2 + v_results[N:2 * N, 2, :]**2, axis=0) velocities_electron = np.mean(np.sqrt(v_results[2 * N:, 0, :]**2 + v_results[2 * N:, 1, :]**2 + v_results[2 * N:, 2, :]**2), axis=0) energies_electron = 0.5 * electron.m * np.mean( v_results[2 * N:, 0, :]**2 + v_results[2 * N:, 1, :]**2 + v_results[2 * N:, 2, :]**2, axis=0) # Plot total velocity and energy results fig, ax = plt.subplots(2, sharex=True) ax[0].plot(t, velocities_p_2, label="ions") ax[0].plot(t, velocities_electron, label="electron") ax[0].plot(t, velocities, label="beam") ax[0].set_ylabel("Velocities [$ms^{-1}$]") ax[1].plot(t, energies_p_2, label="ions") ax[1].plot(t, energies_electron, label="electron") ax[1].plot(t, energies, label="beam") ax[1].plot(t, energies + energies_electron + energies_p_2, label="total") ax[1].set_ylabel("Energies [J]") ax[1].set_xlabel("Time [s]") plt.legend() plt.savefig( os.path.join( res_dir, "{}_{}_{}_{}_{}.png".format(name, N, dt_factor, n, reactant_name))) if plot_individual_sims: plt.show() plt.close() # Plot velocities fig, ax = plt.subplots(2, 2, figsize=(10, 10), sharex='col') v_x_1 = np.mean(v_results[:N, 0, :], axis=0) v_y_1 = np.mean(v_results[:N, 1, :], axis=0) v_z_1 = np.mean(v_results[:N, 2, :], axis=0) v_x_2 = np.mean(v_results[N:2 * N, 0, :], axis=0) v_y_2 = np.mean(v_results[N:2 * N, 1, :], axis=0) v_z_2 = np.mean(v_results[N:2 * N, 2, :], axis=0) v_x_3 = np.mean(v_results[2 * N:, 0, :], axis=0) v_y_3 = np.mean(v_results[2 * N:, 1, :], axis=0) v_z_3 = np.mean(v_results[2 * N:, 2, :], axis=0) ax[0, 0].plot(t, v_x_1, label="v_x_1") ax[0, 0].plot(t, v_x_2, label="v_x_2") ax[0, 0].plot(t, v_x_3, label="v_x_3") ax[0, 0].legend() ax[0, 1].plot(t, v_y_1, label="v_y_1") ax[0, 1].plot(t, v_y_2, label="v_y_2") ax[0, 1].plot(t, v_y_3, label="v_y_3") ax[0, 1].legend() ax[1, 0].plot(t, v_z_1, label="v_z_1") ax[1, 0].plot(t, v_z_2, label="v_z_2") ax[1, 0].plot(t, v_z_3, label="v_z_3") ax[1, 0].legend() ax[1, 1].plot(t, velocities, label="v_t_1") ax[1, 1].plot(t, velocities_p_2, label="v_t_2") ax[1, 1].plot(t, velocities_electron, label="v_t_3") ax[1, 1].legend() plt.savefig( os.path.join( res_dir, "species_velocities_{}_{}_{}_{}_{}.png".format( name, N, dt_factor, n, reactant_name))) if plot_individual_sims: plt.show() plt.close() # Plot temperatures fig, ax = plt.subplots(2, 2, figsize=(10, 10), sharex='col', sharey='row') velocities_total = np.sqrt(v_results[:, 0, :]**2 + v_results[:, 1, :]**2 + v_results[:, 2, :]**2) T_x_1 = np.std(v_results[:N, 0, :], axis=0)**2 * p_1.m / ( PhysicalConstants.boltzmann_constant) T_y_1 = np.std(v_results[:N, 1, :], axis=0)**2 * p_1.m / ( PhysicalConstants.boltzmann_constant) T_z_1 = np.std(v_results[:N, 2, :], axis=0)**2 * p_1.m / ( PhysicalConstants.boltzmann_constant) T_t_1 = np.std(velocities_total[:N, :], axis=0)**2 * p_1.m / ( PhysicalConstants.boltzmann_constant) T_x_2 = np.std(v_results[N:2 * N, 0, :], axis=0)**2 * p_2.m / ( PhysicalConstants.boltzmann_constant) T_y_2 = np.std(v_results[N:2 * N, 1, :], axis=0)**2 * p_2.m / ( PhysicalConstants.boltzmann_constant) T_z_2 = np.std(v_results[N:2 * N, 2, :], axis=0)**2 * p_2.m / ( PhysicalConstants.boltzmann_constant) T_t_2 = np.std(velocities_total[N:2 * N, :], axis=0)**2 * p_2.m / ( PhysicalConstants.boltzmann_constant) T_x_3 = np.std(v_results[2 * N:, 0, :], axis=0)**2 * electron.m / ( PhysicalConstants.boltzmann_constant) T_y_3 = np.std(v_results[2 * N:, 1, :], axis=0)**2 * electron.m / ( PhysicalConstants.boltzmann_constant) T_z_3 = np.std(v_results[2 * N:, 2, :], axis=0)**2 * electron.m / ( PhysicalConstants.boltzmann_constant) T_t_3 = np.std(velocities_total[2 * N:, :], axis=0)**2 * electron.m / ( PhysicalConstants.boltzmann_constant) ax[0, 0].plot(t, T_x_1, label="T_x_1") ax[0, 0].plot(t, T_x_2, label="T_x_2") ax[0, 0].plot(t, T_x_3, label="T_x_3") ax[0, 0].legend() ax[0, 1].plot(t, T_y_1, label="T_y_1") ax[0, 1].plot(t, T_y_2, label="T_y_2") ax[0, 1].plot(t, T_y_3, label="T_y_3") ax[0, 1].legend() ax[1, 0].plot(t, T_z_1, label="T_z_1") ax[1, 0].plot(t, T_z_2, label="T_z_2") ax[1, 0].plot(t, T_z_3, label="T_z_3") ax[1, 0].legend() ax[1, 1].plot(t, T_t_1, label="T_t_1") ax[1, 1].plot(t, T_t_2, label="T_t_2") ax[1, 1].plot(t, T_t_3, label="T_t_3") ax[1, 1].legend() plt.savefig( os.path.join( res_dir, "species_temperatures_{}_{}_{}_{}_{}.png".format( name, N, dt_factor, n, reactant_name))) if plot_individual_sims: plt.show() plt.close() # Get energy half times - assuming beam has equilibrated at the end of the simulation energies = energies - energies[-1] energy_time_interpolator = interp1d(energies / energies[0], t) t_half = energy_time_interpolator(0.5) t_halves[name][i] = t_half t_theory[name][i] = tau # Plot results plt.figure() for name in names: plt.loglog(number_densities, t_halves[name], label="{}_Simulation".format(name)) plt.loglog(number_densities, t_theory[name], label="{}_Theoretical values".format(name)) plt.title("Comparison of thermalisation rates of products and reactants") plt.legend() plt.savefig( os.path.join(res_dir, "energy_half_times_{}".format(reactant_name))) plt.show()
def plot_collisional_angles(): """ Simple function to plot the scattering angles for different binary collisions at they vary with impact parameter ratio """ impact_parameter_ratios = np.linspace(0.1, 10.0, 1000) deuterium = ChargedParticle(2.01410178 * 1.66054e-27, PhysicalConstants.electron_charge) electron = ChargedParticle(9.014e-31, -PhysicalConstants.electron_charge) temperature = 11600.0 # 1eV taken as base case electron_velocity = np.sqrt(PhysicalConstants.boltzmann_constant * temperature / electron.m) deuterium_velocity = np.sqrt(PhysicalConstants.boltzmann_constant * temperature / deuterium.m) print("Electron Thermal Velocity at {}K: {}".format( temperature, electron_velocity)) print("Ion Thermal Velocity at {}K: {}".format(temperature, deuterium_velocity)) collision_pairs = [ ["Deuterium-Deuterium", deuterium, deuterium, deuterium_velocity], ["Electron-Deuterium", electron, deuterium, electron_velocity], ["Electron-Electron", electron, electron, electron_velocity] ] for j, pair in enumerate(collision_pairs): name = pair[0] p_1 = pair[1] p_2 = pair[2] velocity = pair[3] chi = np.zeros(impact_parameter_ratios.shape) cross_section = np.zeros(impact_parameter_ratios.shape) for i, impact_parameter_ratio in enumerate(impact_parameter_ratios): collision = CoulombCollision(p_1, p_2, impact_parameter_ratio, velocity) chi[i] = collision.chi cross_section[i] = collision.differential_cross_section collision = CoulombCollision(p_1, p_2, 1.0, velocity) b_90 = collision.b_90 print("Impact Parameter for {} Collision: {}".format(name, b_90)) # Convert chi to degrees chi /= np.pi chi *= 180.0 # Plot chi vs.impact parameter fig, ax = plt.subplots(2, figsize=(10, 10)) ax[0].plot(impact_parameter_ratios, chi) ax[0].set_title( "Scattering angle vs Impact parameter for {} Collision".format( name)) ax[0].set_xlabel("Impact parameter ratio (b / b_90)") ax[0].set_ylabel("Scattering angle (degrees)") ax[1].semilogy(chi, cross_section) ax[1].set_title( "Cross Section vs Impact parameter for {} Collision".format(name)) ax[1].set_xlabel("Impact parameter ratio (b / b_90)") ax[1].set_ylabel("Number of collisions at given solid angle") plt.show()
def run_electron_beam_into_stationary_target_sim(sim_type): """ Run simulation of velocity relaxation of electron beam to stationary background species """ p_1 = ChargedParticle(PhysicalConstants.electron_mass, -PhysicalConstants.electron_charge) p_2 = ChargedParticle(1e31, -PhysicalConstants.electron_charge) w_1 = 1 w_2 = 10 n = int(1e3) beam_velocity = 1.0 if sim_type == "Abe": sim = AbeCoulombCollisionModel(n, p_1, w_1=w_1, N_2=n, particle_2=p_2, w_2=w_2, freeze_species_2=True) elif sim_type == "Nanbu": sim = NanbuCollisionModel(np.asarray([n, n]), np.asarray([p_1, p_2]), np.asarray([w_1, w_2]), coulomb_logarithm=10.0, frozen_species=[False, True]) else: raise ValueError("Invalid sim type") velocities = np.zeros((2 * n, 3)) velocities[:n, :] = np.asarray([0.0, 0.0, beam_velocity]) velocities[n:] = np.asarray([0.0, 0.0, 0.0]) tau = get_relaxation_time(p_1, n * w_2, beam_velocity) dt = 0.05 * tau final_time = 3.0 * tau t, v_results = sim.run_sim(velocities, dt, final_time) t /= tau v_z_estimate = 1.0 - t v_ort_estimate = 2 * t fig, ax = plt.subplots(2, figsize=(10, 10)) ax[0].plot( t, np.mean(v_results[:n, 0, :]**2 + v_results[:n, 1, :]**2, axis=0) / beam_velocity**2, label="<v_ort^2>") ax[0].plot(t, np.mean(v_results[:n, 2, :], axis=0), label="<v_z>") ax[0].plot(t, v_z_estimate, linestyle="--", label="<v_z_estimate>") ax[0].plot(t, v_ort_estimate, linestyle="--", label="<v_ort_estimate>") ax[0].set_ylim([0.0, 1.0]) ax[0].set_xlim([0.0, t[-1]]) ax[0].legend() ax[0].set_xlabel("Timestep") ax[0].set_ylabel("Velocities ms-1") ax[0].set_title("Beam Velocities") ax[1].plot(t, np.mean(v_results[n:, 0, :], axis=0), label="<v_x>") ax[1].plot(t, np.mean(v_results[n:, 1, :], axis=0), label="<v_y>") ax[1].plot(t, np.mean(v_results[n:, 2, :], axis=0), label="<v_z>") ax[1].legend() ax[1].set_xlabel("Timestep") ax[1].set_ylabel("Velocities ms-1") ax[1].set_title("Background Velocities") plt.show() return t, v_results
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
return 1.0 / stationary_frequency, 1.0 / theoretical_maxwellian_frequency, 1.0 / numerical_maxwellian_frequency, 1.0 / monte_carlo_maxwellian_frequency if __name__ == '__main__': # Define parameter space of scan n = np.logspace(20, 30, 10) T_keV = 10.0 T_K = T_keV * 1e3 * UnitConversions.eV_to_K e = 1.5 * PhysicalConstants.boltzmann_constant * T_K # Set up reaction reaction_name = "pB" if reaction_name == "DD": reaction = DDReaction() deuterium = ChargedParticle(2.01410178 * UnitConversions.amu_to_kg, PhysicalConstants.electron_charge) beam_velocity = np.sqrt(2 * e / deuterium.m) beam_species = deuterium background_species = deuterium elif reaction_name == "DT": reaction = DTReaction() deuterium_tritium = ChargedParticle( 5.0064125184e-27, 2 * PhysicalConstants.electron_charge) beam_velocity = np.sqrt(2 * e / deuterium_tritium.m) beam_species = deuterium_tritium background_species = deuterium_tritium elif reaction_name == "pB": reaction = pBReaction() proton_boron = ChargedParticle((10.81 + 1) * UnitConversions.amu_to_kg,