Source code for mewarpx.mcc_wrapper

import glob
import os

from mewarpx.emission import Injector
# For use later to sync with diode test template and access sim object in mwxrun
from mewarpx.mwxrun import mwxrun
from mewarpx.utils_store import util as mwxutil
from pywarpx import callbacks, picmi


[docs]class MCC(Injector): """Wrapper used to initialize Monte Carlo collision parameters""" 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 _get_particle_data_before(self): """Function to collect particle data before collisions happen but only if that data will be used, otherwise don't waste the time.""" if self.injector_diag is None: return self.prior_weight = self.ion_species.get_total_weight(local=True) self.prior_count = self.ion_species.get_particle_count(local=True) def _get_particle_data_after(self): """Function to collect particle data after collisions happen.""" if self.injector_diag is None: return # the injected weight and count is divided by the processor count # since ``emission.Injector.get_injectedparticles()`` performs a # parallel sum over the injected particle data. injected_weight = ( self.ion_species.get_total_weight(local=True) - self.prior_weight ) injected_count = ( self.ion_species.get_particle_count(local=True) - self.prior_count ) self.record_injectedparticles( species=self.electron_species, w=injected_weight, E_total=0.0, n=injected_count ) self.record_injectedparticles( species=self.ion_species, w=injected_weight, E_total=0.0, n=injected_count )