# MCC collisions cross_sec_direc = '../../../../warpx-data/MCC_cross_sections/He/' mcc_electrons = picmi.MCCCollisions( name='coll_elec', species=electrons, background_density=N_INERT, background_temperature=T_INERT, background_mass=ions.mass, scattering_processes={ 'elastic' : { 'cross_section' : cross_sec_direc+'electron_scattering.dat' }, 'excitation1' : { 'cross_section': cross_sec_direc+'excitation_1.dat', 'energy' : 19.82 }, 'excitation2' : { 'cross_section': cross_sec_direc+'excitation_2.dat', 'energy' : 20.61 }, 'ionization' : { 'cross_section' : cross_sec_direc+'ionization.dat', 'energy' : 24.55, 'species' : ions }, } ) mcc_ions = picmi.MCCCollisions( name='coll_ion',
def __init__(self, electron_species, ion_species, T_INERT, P_INERT=None, N_INERT=None, scraper=None, **kwargs): """Initialize MCC parameters. Arguments: electron_species (picmi.Species): Species that will be producing the ions via impact ionization. This will normally be electrons. ion_species (picmi.Species): Ion species generated from ionization events. Charge state should be specified during Species construction. Also used to obtain the neutral mass. T_INERT (float): Temperature for injected ions in Kelvin. P_INERT (float): Pressure of the neutral "target" for impact ionization, in Torr. Assumed to be such that the density is much larger than both the electron and ion densities, so that the neutral dynamics can be ignored. Cannot be specified if N_INERT is specified. N_INERT (float): Neutral gas density in m^-3. Cannot be specified if P_INERT is specified. scraper (pywarpx.ParticleScraper): The particle scraper is instructed to save pid's for number of MCC events. **kwargs that can be included: exclude_collisions (list): A list of collision types to exclude. """ self.electron_species = electron_species self.ion_species = ion_species self.T_INERT = T_INERT self.N_INERT = N_INERT self.P_INERT = P_INERT self.name = kwargs.get( 'name', f"mcc_{self.electron_species.name}_{self.ion_species.name}") self.exclude_collisions = kwargs.get("exclude_collisions", None) if self.exclude_collisions is None: self.exclude_collisions = [] if self.N_INERT is not None: # N and P cannot both be specified if self.P_INERT is not None: raise ValueError("Must specify N_INERT or P_INERT, not both") # if N is not None and P is None, everything is all good # N and P cannot both be unspecified elif self.P_INERT is None: raise ValueError("Must specify one of N_INERT or P_INERT") # set N using ideal gas law if only P is specified else: self.N_INERT = (mwxutil.ideal_gas_density(self.P_INERT, self.T_INERT)) self.scraper = scraper # Use environment variable if possible, otherwise look one # directory up from warpx path_name = os.environ.get( "MCC_CROSS_SECTIONS_DIR", os.path.join(mwxutil.mewarpx_dir, "../../../warpx-data/MCC_cross_sections")) path_name = os.path.join(path_name, self.ion_species.particle_type) # include all collision processes that match species file_paths = glob.glob(os.path.join(path_name, "*.dat")) elec_collision_types = { "electron_scattering": "elastic", "excitation_1": "excitation1", "excitation_2": "excitation2", "ionization": "ionization", } ion_collision_types = { "ion_scattering": "elastic", "ion_back_scatter": "back", "charge_exchange": "charge_exchange" } required_energy = { "He": { "excitation_1": 19.82, "excitation_2": 20.61, "ionization": 24.55 }, "Ar": { "excitation_1": 11.5, "ionization": 15.7596112 }, "Xe": { "excitation_1": 8.315, "ionization": 12.1298431 } } # build scattering process dictionaries elec_scattering_processes = {} ion_scattering_processes = {} for path in file_paths: file_name = os.path.basename(path) coll_key = file_name.split('.dat')[0] # exclude collision type if specified if coll_key in self.exclude_collisions: continue # if electron process if coll_key in elec_collision_types: coll_type = elec_collision_types[coll_key] scatter_dict = {"cross_section": path} # add energy if needed ion = self.ion_species.particle_type if coll_key in required_energy[ion]: scatter_dict["energy"] = required_energy[ion][coll_key] # specify species for ionization if coll_key == "ionization": scatter_dict["species"] = self.ion_species elec_scattering_processes[coll_type] = scatter_dict # if ion process elif coll_key in ion_collision_types: coll_type = ion_collision_types[coll_key] scatter_dict = {"cross_section": path} ion_scattering_processes[coll_type] = scatter_dict else: raise ValueError( f"{path}: filename not recognized as an MCC cross-section " "file. Please move outside this folder or end with " "something other than .dat if it is not a cross-section " "file.") # raise an error if no scattering processes exist if (not elec_scattering_processes) and (not ion_scattering_processes): raise ValueError( "No scattering processes for electron or ion species.") if mwxrun.simulation.collisions is None: mwxrun.simulation.collisions = [] if elec_scattering_processes: self.electron_mcc = picmi.MCCCollisions( name=f'coll_{self.electron_species.name}', species=self.electron_species, background_density=self.N_INERT, background_temperature=self.T_INERT, background_mass=self.ion_species.mass, scattering_processes=elec_scattering_processes) mwxrun.simulation.collisions.append(self.electron_mcc) if ion_scattering_processes: self.ion_mcc = picmi.MCCCollisions( name=f'coll_{self.ion_species.name}', species=self.ion_species, background_density=self.N_INERT, background_temperature=self.T_INERT, scattering_processes=ion_scattering_processes) mwxrun.simulation.collisions.append(self.ion_mcc) # add E_total PID to both species self.electron_species.add_pid("E_total") self.ion_species.add_pid("E_total") callbacks.installbeforecollisions(self._get_particle_data_before) callbacks.installaftercollisions(self._get_particle_data_after)
def setup_run(self): """Setup simulation components.""" ####################################################################### # Set geometry and boundary conditions # ####################################################################### self.grid = picmi.Cartesian1DGrid( number_of_cells=[self.nz], warpx_max_grid_size=128, lower_bound=[0], upper_bound=[self.gap], lower_boundary_conditions=['dirichlet'], upper_boundary_conditions=['dirichlet'], lower_boundary_conditions_particles=['absorbing'], upper_boundary_conditions_particles=['absorbing'], warpx_potential_hi_z=self.voltage, ) ####################################################################### # Field solver # ####################################################################### self.solver = picmi.ElectrostaticSolver(grid=self.grid, method='Multigrid', required_precision=1e-12, warpx_self_fields_verbosity=0) ####################################################################### # Particle types setup # ####################################################################### self.electrons = picmi.Species( particle_type='electron', name='electrons', initial_distribution=picmi.UniformDistribution( density=self.plasma_density, rms_velocity=[ np.sqrt(constants.kb * self.elec_temp / constants.m_e) ] * 3, )) self.ions = picmi.Species( particle_type='He', name='he_ions', charge='q_e', mass=self.m_ion, initial_distribution=picmi.UniformDistribution( density=self.plasma_density, rms_velocity=[ np.sqrt(constants.kb * self.gas_temp / self.m_ion) ] * 3, )) ####################################################################### # Collision initialization # ####################################################################### cross_sec_direc = '../../../../warpx-data/MCC_cross_sections/He/' mcc_electrons = picmi.MCCCollisions( name='coll_elec', species=self.electrons, background_density=self.gas_density, background_temperature=self.gas_temp, background_mass=self.ions.mass, scattering_processes={ 'elastic': { 'cross_section': cross_sec_direc + 'electron_scattering.dat' }, 'excitation1': { 'cross_section': cross_sec_direc + 'excitation_1.dat', 'energy': 19.82 }, 'excitation2': { 'cross_section': cross_sec_direc + 'excitation_2.dat', 'energy': 20.61 }, 'ionization': { 'cross_section': cross_sec_direc + 'ionization.dat', 'energy': 24.55, 'species': self.ions }, }) mcc_ions = picmi.MCCCollisions( name='coll_ion', species=self.ions, background_density=self.gas_density, background_temperature=self.gas_temp, scattering_processes={ 'elastic': { 'cross_section': cross_sec_direc + 'ion_scattering.dat' }, 'back': { 'cross_section': cross_sec_direc + 'ion_back_scatter.dat' }, # 'charge_exchange' : { # 'cross_section' : cross_sec_direc+'charge_exchange.dat' # } }) ####################################################################### # Initialize simulation # ####################################################################### self.sim = picmi.Simulation( solver=self.solver, time_step_size=self.dt, max_steps=self.max_steps, warpx_collisions=[mcc_electrons, mcc_ions], warpx_load_balance_intervals=self.max_steps // 5000, verbose=self.test) self.sim.add_species(self.electrons, layout=picmi.GriddedLayout( n_macroparticle_per_cell=[self.seed_nppc], grid=self.grid)) self.sim.add_species(self.ions, layout=picmi.GriddedLayout( n_macroparticle_per_cell=[self.seed_nppc], grid=self.grid)) ####################################################################### # Add diagnostics for the CI test to be happy # ####################################################################### field_diag = picmi.FieldDiagnostic( name='diag1', grid=self.grid, period=0, data_list=['rho_electrons', 'rho_he_ions'], write_dir='.', warpx_file_prefix='Python_background_mcc_1d_plt') self.sim.add_diagnostic(field_diag)