aboutsummaryrefslogtreecommitdiff
path: root/Python/pywarpx/PICMI.py
diff options
context:
space:
mode:
authorGravatar Dave Grote <grote1@llnl.gov> 2018-07-10 11:28:22 -0700
committerGravatar Dave Grote <grote1@llnl.gov> 2018-07-10 11:28:22 -0700
commit56b19671f91bd96dbac4962d2cc09e599486acbb (patch)
treefa7b07b144a0f5332dba409734783eee798f0144 /Python/pywarpx/PICMI.py
parentb7e4839001e46aedc2982675cafa128ff893aa18 (diff)
downloadWarpX-56b19671f91bd96dbac4962d2cc09e599486acbb.tar.gz
WarpX-56b19671f91bd96dbac4962d2cc09e599486acbb.tar.zst
WarpX-56b19671f91bd96dbac4962d2cc09e599486acbb.zip
Updates to be consistent with the PICMI standard
Diffstat (limited to 'Python/pywarpx/PICMI.py')
-rw-r--r--Python/pywarpx/PICMI.py373
1 files changed, 208 insertions, 165 deletions
diff --git a/Python/pywarpx/PICMI.py b/Python/pywarpx/PICMI.py
index b1711926f..271d363b9 100644
--- a/Python/pywarpx/PICMI.py
+++ b/Python/pywarpx/PICMI.py
@@ -1,117 +1,52 @@
"""Classes following the PICMI standard
"""
-from PICMI_Base import *
+import PICMI_Base
import numpy as np
-from pywarpx import *
+import pywarpx
codename = 'WarpX'
-class Grid(PICMI_Grid):
- def init(self, **kw):
-
- amr.n_cell = [self.nx, self.ny, self.nz]
-
- # Maximum allowable size of each subdomain in the problem domain;
- # this is used to decompose the domain for parallel calculations.
- amr.max_grid_size = kw.get('max_grid_size', 32)
-
- # Maximum level in hierarchy (for now must be 0, i.e., one level in total)
- amr.max_level = kw.get('max_level', 0)
-
- # Geometry
- geometry.coord_sys = kw.get('coord_sys', 0) # 0: Cartesian
- geometry.is_periodic = '%d %d %d'%(self.bcxmin=='periodic', self.bcymin=='periodic', self.bczmin=='periodic') # Is periodic?
- geometry.prob_lo = [self.xmin, self.ymin, self.zmin] # physical domain
- geometry.prob_hi = [self.xmax, self.ymax, self.zmax]
-
- if self.moving_window_velocity is not None and np.any(self.moving_window_velocity != 0):
- warpx.do_moving_window = 1
- if self.moving_window_velocity[0] != 0.:
- warpx.moving_window_dir = 'x'
- warpx.moving_window_v = self.moving_window_velocity[0]/clight # in units of the speed of light
- if self.moving_window_velocity[1] != 0.:
- warpx.moving_window_dir = 'y'
- warpx.moving_window_v = self.moving_window_velocity[1]/clight # in units of the speed of light
- if self.moving_window_velocity[2] != 0.:
- warpx.moving_window_dir = 'z'
- warpx.moving_window_v = self.moving_window_velocity[2]/clight # in units of the speed of light
-
- def getmins(self, **kw):
- return np.array([warpx.getProbLo(0), warpx.getProbLo(1), warpx.getProbLo(2)])
-
- def getmaxs(self, **kw):
- return np.array([warpx.getProbHi(0), warpx.getProbHi(1), warpx.getProbHi(2)])
-
- def getxmin(self):
- return warpx.getProbLo(0)
- def getxmax(self):
- return warpx.getProbHi(0)
- def getymin(self):
- return warpx.getProbLo(1)
- def getymax(self):
- return warpx.getProbHi(1)
- def getzmin(self):
- return warpx.getProbLo(2)
- def getzmax(self):
- return warpx.getProbHi(2)
-
-
-class EM_solver(PICMI_EM_solver):
- def init(self, **kw):
-
- if self.current_deposition_algo is not None:
- algo.current_deposition = self.current_deposition_algo
- if self.charge_deposition_algo is not None:
- algo.charge_deposition = self.charge_deposition_algo
- if self.field_gathering_algo is not None:
- algo.field_gathering = self.field_gathering_algo
- if self.particle_pusher_algo is not None:
- algo.particle_pusher = self.particle_pusher_algo
+# --- Values from WarpXConst.H
+c = 299792458.
+#ep0 = 8.854187817e-12
+#mu0 = 1.2566370614359173e-06
+#q_e = 1.6021764620000001e-19
+#m_e = 9.10938291e-31
+#m_p = 1.6726231000000001e-27
-class Gaussian_laser(PICMI_Gaussian_laser):
+class Species(PICMI_Base.PICMI_Species):
def init(self, **kw):
- warpx.use_laser = 1
- laser.profile = "Gaussian"
- laser.wavelength = self.wavelength # The wavelength of the laser (in meters)
- laser.e_max = self.E0 # Maximum amplitude of the laser field (in V/m)
- laser.polarization = [np.cos(self.pol_angle), np.sin(self.pol_angle), 0.] # The main polarization vector
- laser.profile_waist = self.waist # The waist of the laser (in meters)
- laser.profile_duration = self.duration # The duration of the laser (in seconds)
- laser.profile_t_peak = (self.focal_position - self.z0)/clight # The time at which the laser reaches its peak (in seconds)
-
-
-class Laser_antenna(PICMI_Laser_antenna):
- def init(self, **kw):
-
- laser.position = [self.antenna_x0, self.antenna_y0, self.antenna_z0] # This point is on the laser plane
- laser.direction = [self.antenna_xvec, self.antenna_yvec, self.antenna_zvec] # The plane normal direction
- laser.profile_focal_distance = self.laser.focal_position - self.antenna_z0 # Focal distance from the antenna (in meters)
-
-
-class Species(PICMI_Species):
- def init(self, **kw):
-
- self.species_number = particles.nspecies
- particles.nspecies = particles.nspecies + 1
- if particles.species_names is None:
- particles.species_names = self.name
+ if self.type == 'electron':
+ if self.charge is None: self.charge = '-q_e'
+ if self.mass is None: self.mass = 'm_e'
+ elif self.type == 'positron':
+ if self.charge is None: self.charge = 'q_e'
+ if self.mass is None: self.mass = 'm_e'
+ elif self.type == 'proton':
+ if self.charge is None: self.charge = 'q_e'
+ if self.mass is None: self.mass = 'm_p'
+ elif self.type == 'anti-proton':
+ if self.charge is None: self.charge = '-q_e'
+ if self.mass is None: self.mass = 'm_p'
+
+ self.species_number = pywarpx.particles.nspecies
+ pywarpx.particles.nspecies += 1
+
+ if self.name is None:
+ self.name = 'species{}'.format(self.species_number)
+
+ if pywarpx.particles.species_names is None:
+ pywarpx.particles.species_names = self.name
else:
- particles.species_names = particles.species_names + ' ' + self.name
-
- self.bucket = Bucket.Bucket(self.name, mass=self.mass, charge=self.charge, injection_style = 'python')
- Particles.particles_list.append(self.bucket)
+ pywarpx.particles.species_names += ' ' + self.name
- def add_particles(self, n=None,
- x=None, y=None, z=None,
- ux=None, uy=None, uz=None, w=None,
- unique_particles=None, **kw):
- pid = np.array([w]).T
- add_particles(self.species_number, x, y, z, ux, uy, uz, pid, unique_particles)
+ self.bucket = pywarpx.Bucket.Bucket(self.name, mass=self.mass, charge=self.charge, injection_style = 'python')
+ pywarpx.Particles.particles_list.append(self.bucket)
-class GaussianBeam(PICMI_GaussianBeam):
+class GaussianBeam(PICMI_Base.PICMI_GaussianBeam):
def init(self, **kw):
self.species.bucket.injection_style = "gaussian_beam"
@@ -122,41 +57,55 @@ class GaussianBeam(PICMI_GaussianBeam):
self.species.bucket.y_rms = self.Yrms
self.species.bucket.z_rms = self.Zrms
self.species.bucket.npart = self.number_sim_particles
- self.species.bucket.q_tot = self.number_sim_particles*self.species.charge
+ self.species.bucket.q_tot = self.number_real_particles*self.species.bucket.charge
- # --- These are unused but need to be set (maybe)
- self.species.bucket.profile = 'constant'
+ # --- These need to be defined even though they are not used
+ self.species.bucket.profile = "constant"
self.species.bucket.density = 1
- # --- Momentum distribution
- if 'u_over_r' in kw:
- # --- Radial expansion
+ # --- The PICMI standard doesn't yet have a way of specifying these values.
+ # --- They should default to the size of the domain. They are not typically
+ # --- necessary though since any particles outside the domain are rejected.
+ #self.species.bucket.xmin
+ #self.species.bucket.xmax
+ #self.species.bucket.ymin
+ #self.species.bucket.ymax
+ #self.species.bucket.zmin
+ #self.species.bucket.zmax
+
+ if self.UXdiv != 0. and UYdiv != 0. and UZdiv != 0.:
self.species.bucket.momentum_distribution_type = "radial_expansion"
- self.species.bucket.u_over_r = kw['u_over_r']
-
- elif self.UXrms == 0. and self.UYrms == 0. and self.UZrms == 0.:
- # --- Constant velocity
- self.species.bucket.momentum_distribution_type = "constant"
- self.species.bucket.ux = self.UXmean
- self.species.bucket.uy = self.UYmean
- self.species.bucket.uz = self.UZmean
-
- else:
- # --- Gaussian velocity distribution
+ self.species.bucket.u_over_r = self.UXdiv
+ #self.species.bucket.u_over_y = self.UYdiv
+ #self.species.bucket.u_over_z = self.UZdiv
+ elif self.UXrms != 0. or UYrms != 0. or UZrms != 0.:
self.species.bucket.momentum_distribution_type = "gaussian"
self.species.bucket.ux_m = self.UXmean
self.species.bucket.uy_m = self.UYmean
self.species.bucket.uz_m = self.UZmean
- self.species.bucket.u_th = self.UZrms
- # !!! UXrms and UYrms are unused. Only an isotropic distribution is supported
- # !!! Maybe an error should be raised
+ self.species.bucket.ux_th = self.UXrms
+ self.species.bucket.uy_th = self.UYrms
+ self.species.bucket.uz_th = self.UZrms
+ else:
+ self.species.bucket.momentum_distribution_type = "constant"
+ self.species.bucket.ux = self.UXmean
+ self.species.bucket.uy = self.UYmean
+ self.species.bucket.uz = self.UZmean
-class Plasma(PICMI_Plasma):
+class Plasma(PICMI_Base.PICMI_Plasma):
def init(self, **kw):
for species in self.species:
- species.bucket.injection_style = "NUniformPerCell"
+ if self.number_per_cell_each_dim is not None:
+ species.bucket.injection_style = "nuniformpercell"
+ species.bucket.num_particles_per_cell_each_dim = self.number_per_cell_each_dim
+ elif self.number_per_cell is not None:
+ species.bucket.injection_style = "nrandompercell"
+ species.bucket.num_particles_per_cell = self.number_per_cell
+ else:
+ raise Exception('Either nuniformpercell or nrandompercell must be specified')
+
species.bucket.xmin = self.xmin
species.bucket.xmax = self.xmax
species.bucket.ymin = self.ymin
@@ -164,78 +113,150 @@ class Plasma(PICMI_Plasma):
species.bucket.zmin = self.zmin
species.bucket.zmax = self.zmax
- species.bucket.profile = 'constant'
+ # --- Only constant density is supported at this time
+ species.bucket.profile = "constant"
species.bucket.density = self.density
- if self.number_per_cell is not None:
- species.bucket.nrandompercell = self.number_per_cell
- elif self.number_per_cell_each_dim is not None:
- species.bucket.num_particles_per_cell_each_dim = self.number_per_cell_each_dim
-
- # --- Momentum distribution
- if 'u_over_r' in kw:
- # --- Radial expansion
- species.bucket.momentum_distribution_type = "radial_expansion"
- species.bucket.u_over_r = kw['u_over_r']
-
- elif self.vthx == 0. and self.vthy == 0. and self.vthz == 0.:
- # --- Constant velocity
+ if self.vthx != 0. or self.vthy != 0. or self.vthz != 0.:
+ species.bucket.momentum_distribution_type = "gaussian"
+ species.bucket.ux_m = self.vxmean
+ species.bucket.uy_m = self.vymean
+ species.bucket.uz_m = self.vzmean
+ species.bucket.ux_th = self.vthx
+ species.bucket.uy_th = self.vthy
+ species.bucket.uz_th = self.vthz
+ else:
species.bucket.momentum_distribution_type = "constant"
species.bucket.ux = self.vxmean
species.bucket.uy = self.vymean
species.bucket.uz = self.vzmean
- else:
- # --- Gaussian velocity distribution
- species.bucket.momentum_distribution_type = "gaussian"
- species.bucket.ux_m = self.vxmean
- species.bucket.uy_m = self.vymean
- species.bucket.uz_m = self.vzmean
- species.bucket.u_th = self.vthz
- # !!! vthx and vthy are unused. Only an isotropic distribution is supported
- # !!! Maybe an error should be raised
+ if self.fill_in:
+ pywarpx.warpx.do_plasma_injection = 1
+ if not hasattr(pywarpx.warpx, 'injected_plasma_species'):
+ pywarpx.warpx.injected_plasma_species = []
+ pywarpx.warpx.injected_plasma_species.append(species.species_number)
+ pywarpx.warpx.num_injected_species = len(pywarpx.warpx.injected_plasma_species)
-class ParticleList(PICMI_ParticleList):
+
+class ParticleList(PICMI_Base.PICMI_ParticleList):
def init(self, **kw):
- assert len(self.x) == 1, "WarpX only supports initializing with a single particle"
+ if len(x) > 1:
+ raise Exception('Only a single particle can be loaded')
- self.species.bucket.injection_style = "SingleParticle"
+ self.species.bucket.injection_style = "singleparticle"
self.species.bucket.single_particle_pos = [self.x[0], self.y[0], self.z[0]]
- self.species.bucket.single_particle_vel = [self.ux[0]/clight, self.uy[0]/clight, self.uz[0]/clight]
+ self.species.bucket.single_particle_vel = [self.ux[0]/c, self.uy[0]/c, self.uz[0]/c]
self.species.bucket.single_particle_weight = self.weight
+ # --- These need to be defined even though they are not used
+ self.species.bucket.profile = "constant"
+ self.species.bucket.density = 1
+ self.species.bucket.momentum_distribution_type = 'constant'
+
+
+class Grid(PICMI_Base.PICMI_Grid):
+ def init(self, **kw):
+
+ pywarpx.amr.n_cell = [self.nx, self.ny, self.nz]
+
+ # Maximum allowable size of each subdomain in the problem domain;
+ # this is used to decompose the domain for parallel calculations.
+ pywarpx.amr.max_grid_size = kw.get('max_grid_size', 32)
+
+ # Maximum level in hierarchy (for now must be 0, i.e., one level in total)
+ pywarpx.amr.max_level = kw.get('max_level', 0)
+
+ # Geometry
+ pywarpx.geometry.coord_sys = kw.get('coord_sys', 0) # 0: Cartesian
+ pywarpx.geometry.is_periodic = '%d %d %d'%(self.bcxmin=='periodic', self.bcymin=='periodic', self.bczmin=='periodic') # Is periodic?
+ pywarpx.geometry.prob_lo = [self.xmin, self.ymin, self.zmin] # physical domain
+ pywarpx.geometry.prob_hi = [self.xmax, self.ymax, self.zmax]
+
+ if self.moving_window_velocity is not None and np.any(np.not_equal(self.moving_window_velocity, 0.)):
+ pywarpx.warpx.do_moving_window = 1
+ if self.moving_window_velocity[0] != 0.:
+ pywarpx.warpx.moving_window_dir = 'x'
+ pywarpx.warpx.moving_window_v = self.moving_window_velocity[0]/c # in units of the speed of light
+ if self.moving_window_velocity[1] != 0.:
+ pywarpx.warpx.moving_window_dir = 'y'
+ pywarpx.warpx.moving_window_v = self.moving_window_velocity[1]/c # in units of the speed of light
+ if self.moving_window_velocity[2] != 0.:
+ pywarpx.warpx.moving_window_dir = 'z'
+ pywarpx.warpx.moving_window_v = self.moving_window_velocity[2]/c # in units of the speed of light
+
+ def getmins(self, **kw):
+ return np.array([pywarpx.warpx.getProbLo(0), pywarpx.warpx.getProbLo(1), pywarpx.warpx.getProbLo(2)])
+
+ def getmaxs(self, **kw):
+ return np.array([pywarpx.warpx.getProbHi(0), pywarpx.warpx.getProbHi(1), pywarpx.warpx.getProbHi(2)])
+
+ def getxmin(self):
+ return pywarpx.warpx.getProbLo(0)
+
+ def getxmax(self):
+ return pywarpx.warpx.getProbHi(0)
+
+ def getymin(self):
+ return pywarpx.warpx.getProbLo(1)
+
+ def getymax(self):
+ return pywarpx.warpx.getProbHi(1)
+
+ def getzmin(self):
+ return pywarpx.warpx.getProbLo(2)
+
+ def getzmax(self):
+ return pywarpx.warpx.getProbHi(2)
+
+
+class EM_solver(PICMI_Base.PICMI_EM_solver):
+ def init(self, **kw):
+
+ if self.method is None:
+ self.method = 'Yee'
-class Simulation(PICMI_Simulation):
- def set_warpx_attr(self, warpx_obj, attr, kw):
- value = kw.get(attr, None)
- if value is not None:
- setattr(warpx_obj, attr, value)
- setattr(self, attr, value)
+ assert self.method in ['Yee'], Exception("Only 'Yee' FDTD is supported")
+ if 'current_deposition_algo' in kw:
+ pywarpx.algo.current_deposition = kw['current_deposition_algo']
+ if 'charge_deposition_algo' in kw:
+ pywarpx.algo.charge_deposition = kw['charge_deposition_algo']
+ if 'field_gathering_algo' in kw:
+ pywarpx.algo.field_gathering = kw['field_gathering_algo']
+ if 'particle_pusher_algo' in kw:
+ pywarpx.algo.particle_pusher = kw['particle_pusher_algo']
+
+
+class Simulation(PICMI_Base.PICMI_Simulation):
def init(self, **kw):
- warpx.verbose = self.verbose
- warpx.cfl = self.timestep_over_cfl
+ pywarpx.warpx.verbose = self.verbose
+ pywarpx.warpx.cfl = self.timestep_over_cfl
if self.timestep == 0.:
- warpx.cfl = self.timestep_over_cfl
+ pywarpx.warpx.cfl = self.timestep_over_cfl
else:
- warpx.const_dt = self.timestep
- amr.plot_int = self.plot_int
+ pywarpx.warpx.const_dt = self.timestep
+
+ if 'plot_int' in kw:
+ pywarpx.amr.plot_int = kw['plot_int']
self.initialized = False
def initialize(self, inputs_name=None):
if not self.initialized:
self.initialized = True
- warpx.init()
+ pywarpx.warpx.init()
def write_inputs(self, inputs_name='inputs'):
kw = {}
- if hasattr(self, 'max_step'):
+ if self.max_step is not None:
kw['max_step'] = self.max_step
- warpx.write_inputs(inputs_name, **kw)
+ if self.max_time is not None:
+ kw['stop_time'] = self.max_time
+ pywarpx.warpx.write_inputs(inputs_name, **kw)
def step(self, nsteps=None):
self.initialize()
@@ -244,8 +265,30 @@ class Simulation(PICMI_Simulation):
nsteps = self.max_step
else:
nsteps = -1
- warpx.evolve(nsteps)
+ pywarpx.warpx.evolve(nsteps)
def finalize(self):
- warpx.finalize()
+ if self.initialized:
+ self.initialized = False
+ pywarpx.warpx.finalize()
+
+
+class Gaussian_laser(PICMI_Base.PICMI_Gaussian_laser):
+ def init(self, **kw):
+
+ pywarpx.warpx.use_laser = 1
+ pywarpx.laser.profile = "Gaussian"
+ pywarpx.laser.wavelength = self.wavelength # The wavelength of the laser (in meters)
+ pywarpx.laser.e_max = self.E0 # Maximum amplitude of the laser field (in V/m)
+ pywarpx.laser.polarization = [np.cos(self.pol_angle), np.sin(self.pol_angle), 0.] # The main polarization vector
+ pywarpx.laser.profile_waist = self.waist # The waist of the laser (in meters)
+ pywarpx.laser.profile_duration = self.duration # The duration of the laser (in seconds)
+ pywarpx.laser.profile_t_peak = (self.focal_position - self.z0)/c # The time at which the laser reaches its peak (in seconds)
+
+
+class Laser_antenna(PICMI_Base.PICMI_Laser_antenna):
+ def init(self, **kw):
+ pywarpx.laser.position = [self.antenna_x0, self.antenna_y0, self.antenna_z0] # This point is on the laser plane
+ pywarpx.laser.direction = [self.antenna_xvec, self.antenna_yvec, self.antenna_zvec] # The plane normal direction
+ pywarpx.laser.profile_focal_distance = self.laser.focal_position - self.antenna_z0 # Focal distance from the antenna (in meters)