|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# |
| 3 | +# --- Test script for the effective potential Poisson solver. This test is based |
| 4 | +# --- on the adiabatic plasma expansion benchmark from Connor et al. (2021) |
| 5 | +# --- doi.org/10.1109/TPS.2021.3072353. |
| 6 | +# --- In the benchmark an expanding plasma ball with Gaussian density distribution |
| 7 | +# --- in the radial direction is simulated and the time evolution of the |
| 8 | +# --- density of the electron species is compared to an approximate analytic solution. |
| 9 | +# --- The example is modified slightly in the following ways: |
| 10 | +# --- 1) The original example used an electromagnetic solver with absorbing |
| 11 | +# --- boundaries while the present case encloses the plasma in a conducting |
| 12 | +# --- sphere. |
| 13 | +# --- 2) The domain and plasma parameters for this case has been modified to |
| 14 | +# --- set the cell-size and time step such that the explicit electrostatic |
| 15 | +# --- solver is unstable. |
| 16 | + |
| 17 | +import dill |
| 18 | +import numpy as np |
| 19 | +from mpi4py import MPI as mpi |
| 20 | +from scipy.special import erf |
| 21 | + |
| 22 | +from pywarpx import picmi |
| 23 | + |
| 24 | +constants = picmi.constants |
| 25 | + |
| 26 | +comm = mpi.COMM_WORLD |
| 27 | + |
| 28 | +simulation = picmi.Simulation(warpx_serialize_initial_conditions=True, verbose=0) |
| 29 | + |
| 30 | +m_ion = 25 # Ion mass (electron masses) |
| 31 | + |
| 32 | +# Plasma parameters |
| 33 | +n_plasma = 5e12 # Plasma density (m^-3) |
| 34 | +sigma_0 = 22 # Initial Gaussian distribution standard deviation (Debye lengths) |
| 35 | +T_e = 100.0 # Electron temperature (K) |
| 36 | +T_i = 10.0 # Ion temperature (K) |
| 37 | + |
| 38 | +# Spatial domain |
| 39 | +R = 86 # Radius of metallic sphere (Debye lengths) |
| 40 | +NZ = 72 # Number of cells in each direction |
| 41 | + |
| 42 | +# Temporal domain (if not run as a CI test) |
| 43 | +LT = 0.6e-6 # Simulation temporal length (s) |
| 44 | + |
| 45 | +# Numerical parameters |
| 46 | +NPARTS = 500000 # Seed number of particles |
| 47 | +DT = 0.8 # Time step (electron streaming) |
| 48 | + |
| 49 | +# Solver parameter |
| 50 | +C_EP = 1.0 # Effective potential factor |
| 51 | + |
| 52 | +####################################################################### |
| 53 | +# Calculate various plasma parameters based on the simulation input # |
| 54 | +####################################################################### |
| 55 | + |
| 56 | +# Ion mass (kg) |
| 57 | +M = m_ion * constants.m_e |
| 58 | + |
| 59 | +# Electron plasma frequency (Hz) |
| 60 | +f_pe = np.sqrt(constants.q_e**2 * n_plasma / (constants.m_e * constants.ep0)) / ( |
| 61 | + 2.0 * np.pi |
| 62 | +) |
| 63 | + |
| 64 | +# Debye length (m) |
| 65 | +lambda_e = np.sqrt(constants.ep0 * constants.kb * T_e / (n_plasma * constants.q_e**2)) |
| 66 | + |
| 67 | +# Thermal velocities (m/s) from v_th = np.sqrt(kT / m) |
| 68 | +v_ti = np.sqrt(T_i * constants.kb / M) |
| 69 | +v_te = np.sqrt(T_e * constants.kb / constants.m_e) |
| 70 | + |
| 71 | +R *= lambda_e |
| 72 | +sigma_0 *= lambda_e |
| 73 | + |
| 74 | +dz = 2.0 * R / (NZ - 4) |
| 75 | +Lz = dz * NZ |
| 76 | +dt = DT * dz / v_te |
| 77 | + |
| 78 | +total_steps = int(LT / dt) |
| 79 | +diag_steps = total_steps // 3 |
| 80 | +total_steps = diag_steps * 3 |
| 81 | + |
| 82 | +# dump attributes needed for analysis to a dill pickle file |
| 83 | +if comm.rank == 0: |
| 84 | + parameter_dict = { |
| 85 | + "sigma_0": sigma_0, |
| 86 | + "M": M, |
| 87 | + "T_i": T_i, |
| 88 | + "T_e": T_e, |
| 89 | + "n_plasma": n_plasma, |
| 90 | + } |
| 91 | + with open("sim_parameters.dpkl", "wb") as f: |
| 92 | + dill.dump(parameter_dict, f) |
| 93 | + |
| 94 | +# print out plasma parameters |
| 95 | +if comm.rank == 0: |
| 96 | + print( |
| 97 | + f"Initializing simulation with input parameters:\n" |
| 98 | + f"\tT_e = {T_e:.1f} K\n" |
| 99 | + f"\tT_i = {T_i:.1f} K\n" |
| 100 | + f"\tn = {n_plasma:.1e} m^-3\n" |
| 101 | + ) |
| 102 | + print( |
| 103 | + f"Plasma parameters:\n" |
| 104 | + f"\tlambda_e = {lambda_e:.1e} m\n" |
| 105 | + f"\tt_pe = {1.0/f_pe:.1e} s\n" |
| 106 | + f"\tv_ti = {v_ti:.1e} m/s\n" |
| 107 | + ) |
| 108 | + print( |
| 109 | + f"Numerical parameters:\n" |
| 110 | + f"\tdz/lambda_e = {dz/lambda_e:.2f}\n" |
| 111 | + f"\tdt*w_pe = {dt*f_pe*2.0*np.pi:.2f}\n" |
| 112 | + f"\tdiag steps = {diag_steps:d}\n" |
| 113 | + f"\ttotal steps = {total_steps:d}\n" |
| 114 | + ) |
| 115 | + |
| 116 | + |
| 117 | +####################################################################### |
| 118 | +# Set geometry and boundary conditions # |
| 119 | +####################################################################### |
| 120 | + |
| 121 | +grid = picmi.Cartesian3DGrid( |
| 122 | + number_of_cells=[NZ] * 3, |
| 123 | + lower_bound=[-Lz / 2.0] * 3, |
| 124 | + upper_bound=[Lz / 2.0] * 3, |
| 125 | + lower_boundary_conditions=["neumann"] * 3, |
| 126 | + upper_boundary_conditions=["neumann"] * 3, |
| 127 | + lower_boundary_conditions_particles=["absorbing"] * 3, |
| 128 | + upper_boundary_conditions_particles=["absorbing"] * 3, |
| 129 | + warpx_max_grid_size=NZ // 2, |
| 130 | +) |
| 131 | +simulation.time_step_size = dt |
| 132 | +simulation.max_steps = total_steps |
| 133 | +simulation.current_deposition_algo = "direct" |
| 134 | +simulation.particle_shape = 1 |
| 135 | +simulation.verbose = 1 |
| 136 | + |
| 137 | +####################################################################### |
| 138 | +# Insert spherical boundary as EB # |
| 139 | +####################################################################### |
| 140 | + |
| 141 | +embedded_boundary = picmi.EmbeddedBoundary( |
| 142 | + implicit_function=f"(x**2+y**2+z**2-{R**2})", |
| 143 | + potential=0.0, |
| 144 | +) |
| 145 | +simulation.embedded_boundary = embedded_boundary |
| 146 | + |
| 147 | +####################################################################### |
| 148 | +# Field solver # |
| 149 | +####################################################################### |
| 150 | + |
| 151 | +solver = picmi.ElectrostaticSolver( |
| 152 | + grid=grid, |
| 153 | + method="Multigrid", |
| 154 | + warpx_effective_potential=True, |
| 155 | + warpx_effective_potential_factor=C_EP, |
| 156 | + warpx_self_fields_verbosity=0, |
| 157 | +) |
| 158 | +simulation.solver = solver |
| 159 | + |
| 160 | +####################################################################### |
| 161 | +# Particle types setup # |
| 162 | +####################################################################### |
| 163 | + |
| 164 | +total_parts = ( |
| 165 | + n_plasma |
| 166 | + * sigma_0**2 |
| 167 | + * ( |
| 168 | + (2.0 * np.pi) ** 1.5 * sigma_0 * erf(R / (np.sqrt(2) * sigma_0)) |
| 169 | + + 4.0 * np.pi * R * np.exp(-(R**2) / (2.0 * sigma_0**2)) |
| 170 | + ) |
| 171 | +) |
| 172 | + |
| 173 | +electrons = picmi.Species( |
| 174 | + name="electrons", |
| 175 | + particle_type="electron", |
| 176 | + initial_distribution=picmi.GaussianBunchDistribution( |
| 177 | + n_physical_particles=total_parts, |
| 178 | + rms_bunch_size=[sigma_0] * 3, |
| 179 | + rms_velocity=[v_te] * 3, |
| 180 | + ), |
| 181 | +) |
| 182 | +simulation.add_species( |
| 183 | + electrons, |
| 184 | + layout=picmi.PseudoRandomLayout(grid=grid, n_macroparticles=NPARTS), |
| 185 | +) |
| 186 | + |
| 187 | +ions = picmi.Species( |
| 188 | + name="ions", |
| 189 | + charge="q_e", |
| 190 | + mass=M, |
| 191 | + initial_distribution=picmi.GaussianBunchDistribution( |
| 192 | + n_physical_particles=total_parts, |
| 193 | + rms_bunch_size=[sigma_0] * 3, |
| 194 | + rms_velocity=[v_ti] * 3, |
| 195 | + ), |
| 196 | +) |
| 197 | +simulation.add_species( |
| 198 | + ions, |
| 199 | + layout=picmi.PseudoRandomLayout(grid=grid, n_macroparticles=NPARTS), |
| 200 | +) |
| 201 | + |
| 202 | +####################################################################### |
| 203 | +# Add diagnostics # |
| 204 | +####################################################################### |
| 205 | + |
| 206 | +field_diag = picmi.FieldDiagnostic( |
| 207 | + name="field_diag", |
| 208 | + grid=grid, |
| 209 | + period=diag_steps, |
| 210 | + data_list=[ |
| 211 | + "E", |
| 212 | + "J", |
| 213 | + "T_electrons", |
| 214 | + "T_ions", |
| 215 | + "phi", |
| 216 | + "rho_electrons", |
| 217 | + "rho_ions", |
| 218 | + ], |
| 219 | + write_dir="diags", |
| 220 | + warpx_format="openpmd", |
| 221 | + warpx_openpmd_backend="h5", |
| 222 | +) |
| 223 | +simulation.add_diagnostic(field_diag) |
| 224 | + |
| 225 | +####################################################################### |
| 226 | +# Initialize simulation # |
| 227 | +####################################################################### |
| 228 | + |
| 229 | +simulation.initialize_inputs() |
| 230 | +simulation.initialize_warpx() |
| 231 | + |
| 232 | +####################################################################### |
| 233 | +# Execute simulation # |
| 234 | +####################################################################### |
| 235 | + |
| 236 | +simulation.step() |
0 commit comments