diff options
Diffstat (limited to 'Python/pywarpx')
-rw-r--r-- | Python/pywarpx/AMReX.py | 16 | ||||
-rw-r--r-- | Python/pywarpx/Bucket.py | 7 | ||||
-rw-r--r-- | Python/pywarpx/PGroup.py | 5 | ||||
-rw-r--r-- | Python/pywarpx/PICMI.py | 160 | ||||
-rw-r--r-- | Python/pywarpx/Particles.py | 2 | ||||
-rw-r--r-- | Python/pywarpx/WarpX.py | 50 | ||||
-rw-r--r-- | Python/pywarpx/WarpXPIC.py | 50 | ||||
-rw-r--r-- | Python/pywarpx/__init__.py | 6 | ||||
-rw-r--r-- | Python/pywarpx/callbacks.py | 481 | ||||
-rw-r--r-- | Python/pywarpx/timestepper.py | 329 |
10 files changed, 758 insertions, 348 deletions
diff --git a/Python/pywarpx/AMReX.py b/Python/pywarpx/AMReX.py index 4ce7ac61c..55c11c432 100644 --- a/Python/pywarpx/AMReX.py +++ b/Python/pywarpx/AMReX.py @@ -16,8 +16,8 @@ from ._libwarpx import amrex_init class AMReX(object): - def init(self): - argv = ['warpx'] + def create_argv_list(self): + argv = [] argv += warpx.attrlist() argv += amr.attrlist() argv += geometry.attrlist() @@ -36,7 +36,19 @@ class AMReX(object): for particle in particles_list: argv += particle.attrlist() + return argv + + def init(self): + argv = ['warpx'] + self.create_argv_list() amrex_init(argv) def finalize(self, finalize_mpi=1): libwarpx.amrex_finalize(finalize_mpi) + + def write_inputs(self, filename='inputs'): + argv = self.create_argv_list() + with open(filename, 'w') as ff: + + for arg in argv: + ff.write('{0}\n'.format(arg)) + diff --git a/Python/pywarpx/Bucket.py b/Python/pywarpx/Bucket.py index 5c429f530..960fe2108 100644 --- a/Python/pywarpx/Bucket.py +++ b/Python/pywarpx/Bucket.py @@ -28,7 +28,12 @@ class Bucket(object): for attr, value in iteritems(self.argvattrs): # --- repr is applied to value so that for floats, all of the digits are included. # --- The strip is then needed when value is a string. - attrstring = '{0}.{1}={2} '.format(self.instancename, attr, repr(value).strip("'\"")) + if hasattr(value, '__iter__'): + # --- For lists, tuples, and arrays make a space delimited string of the values + rhs = ' '.join(map(repr, value)) + else: + rhs = value + attrstring = '{0}.{1}={2}'.format(self.instancename, attr, repr(rhs).strip("'\"")) result += [attrstring] return result diff --git a/Python/pywarpx/PGroup.py b/Python/pywarpx/PGroup.py index fa8572c0a..64e396d38 100644 --- a/Python/pywarpx/PGroup.py +++ b/Python/pywarpx/PGroup.py @@ -148,7 +148,7 @@ class PGroups(object): self._pgroups = [] for igroup in range(self.ngroups): self._pgroups.append(PGroup(igroup, self.ispecie)) - + def __iter__(self): self.setuppgroups() for igroup in range(self.ngroups): @@ -158,3 +158,6 @@ class PGroups(object): self.setuppgroups() return self._pgroups[key] + def __len__(self): + self.setuppgroups() + return len(self._pgroups) diff --git a/Python/pywarpx/PICMI.py b/Python/pywarpx/PICMI.py index 1bc36e5ca..b1711926f 100644 --- a/Python/pywarpx/PICMI.py +++ b/Python/pywarpx/PICMI.py @@ -6,14 +6,10 @@ from pywarpx import * codename = 'WarpX' -def _args_to_string(*args): - # --- Converts of sequence of number to a string that is appropriate for input. - return ' '.join(map(repr, args)) - class Grid(PICMI_Grid): def init(self, **kw): - amr.n_cell = _args_to_string(self.nx, self.ny, self.nz) + 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. @@ -25,8 +21,8 @@ class Grid(PICMI_Grid): # 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 = _args_to_string(self.xmin, self.ymin, self.zmin) # physical domain - geometry.prob_hi = _args_to_string(self.xmax, self.ymax, self.zmax) + 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 @@ -78,15 +74,20 @@ class Gaussian_laser(PICMI_Gaussian_laser): warpx.use_laser = 1 laser.profile = "Gaussian" - laser.position = _args_to_string(self.antenna_x0, self.antenna_y0, self.antenna_z0) # This point is on the laser plane - laser.direction = _args_to_string(self.antenna_xvec, self.antenna_yvec, self.antenna_zvec) # The plane normal direction - laser.polarization = _args_to_string(np.cos(self.pol_angle), np.sin(self.pol_angle), 0.) # The main polarization vector + 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.t_peak # The time at which the laser reaches its peak (in seconds) - laser.profile_focal_distance = self.focal_position - self.antenna_z0 # Focal distance from the antenna (in meters) - laser.wavelength = self.wavelength # The wavelength of the laser (in meters) + 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): @@ -94,7 +95,10 @@ class Species(PICMI_Species): self.species_number = particles.nspecies particles.nspecies = particles.nspecies + 1 - particles.species_names = particles.species_names + ' ' + self.name + if particles.species_names is None: + 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) @@ -107,6 +111,102 @@ class Species(PICMI_Species): add_particles(self.species_number, x, y, z, ux, uy, uz, pid, unique_particles) +class GaussianBeam(PICMI_GaussianBeam): + def init(self, **kw): + + self.species.bucket.injection_style = "gaussian_beam" + self.species.bucket.x_m = self.Xmean + self.species.bucket.y_m = self.Ymean + self.species.bucket.z_m = self.Zmean + self.species.bucket.x_rms = self.Xrms + 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 + + # --- These are unused but need to be set (maybe) + self.species.bucket.profile = 'constant' + self.species.bucket.density = 1 + + # --- Momentum distribution + if 'u_over_r' in kw: + # --- Radial expansion + 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.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 + + +class Plasma(PICMI_Plasma): + def init(self, **kw): + + for species in self.species: + species.bucket.injection_style = "NUniformPerCell" + species.bucket.xmin = self.xmin + species.bucket.xmax = self.xmax + species.bucket.ymin = self.ymin + species.bucket.ymax = self.ymax + species.bucket.zmin = self.zmin + species.bucket.zmax = self.zmax + + 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 + 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 + + +class ParticleList(PICMI_ParticleList): + def init(self, **kw): + + assert len(self.x) == 1, "WarpX only supports initializing with a single particle" + + 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_weight = self.weight + + class Simulation(PICMI_Simulation): def set_warpx_attr(self, warpx_obj, attr, kw): value = kw.get(attr, None) @@ -117,17 +217,35 @@ class Simulation(PICMI_Simulation): def init(self, **kw): warpx.verbose = self.verbose - warpx.cfl = self.cfl + warpx.cfl = self.timestep_over_cfl + if self.timestep == 0.: + warpx.cfl = self.timestep_over_cfl + else: + warpx.const_dt = self.timestep amr.plot_int = self.plot_int - self.amrex = AMReX() - self.amrex.init() - warpx.init() - - def step(self, nsteps=-1): + self.initialized = False + + def initialize(self, inputs_name=None): + if not self.initialized: + self.initialized = True + warpx.init() + + def write_inputs(self, inputs_name='inputs'): + kw = {} + if hasattr(self, 'max_step'): + kw['max_step'] = self.max_step + warpx.write_inputs(inputs_name, **kw) + + def step(self, nsteps=None): + self.initialize() + if nsteps is None: + if self.max_step is not None: + nsteps = self.max_step + else: + nsteps = -1 warpx.evolve(nsteps) def finalize(self): warpx.finalize() - self.amrex.finalize() diff --git a/Python/pywarpx/Particles.py b/Python/pywarpx/Particles.py index e7bac7e10..395a888d7 100644 --- a/Python/pywarpx/Particles.py +++ b/Python/pywarpx/Particles.py @@ -1,6 +1,6 @@ from .Bucket import Bucket -particles = Bucket('particles', nspecies=0, species_names='') +particles = Bucket('particles', nspecies=0, species_names=None) particles_list = [] electrons = Bucket('electrons') diff --git a/Python/pywarpx/WarpX.py b/Python/pywarpx/WarpX.py index f0a31f3f9..7a05c40fb 100644 --- a/Python/pywarpx/WarpX.py +++ b/Python/pywarpx/WarpX.py @@ -1,19 +1,56 @@ from .Bucket import Bucket + +from .Amr import amr +from .Geometry import geometry +from .Algo import algo +from .Langmuirwave import langmuirwave +from .Interpolation import interpolation +from .Laser import laser +from . import Particles +from .Particles import particles, particles_list + +import ctypes from ._libwarpx import libwarpx +from ._libwarpx import amrex_init class WarpX(Bucket): """ A Python wrapper for the WarpX C++ class """ + def create_argv_list(self): + argv = [] + argv += warpx.attrlist() + argv += amr.attrlist() + argv += geometry.attrlist() + argv += algo.attrlist() + argv += langmuirwave.attrlist() + argv += interpolation.attrlist() + argv += particles.attrlist() + argv += laser.attrlist() + + if not particles_list: + # --- This is needed in case only species_names has been set, + # --- assuming that only the built in particle types are being used. + for pstring in particles.species_names.split(' '): + particles_list.append(getattr(Particles, pstring)) + + for particle in particles_list: + argv += particle.attrlist() + + return argv + def init(self): + argv = ['warpx'] + self.create_argv_list() + amrex_init(argv) libwarpx.warpx_init() def evolve(self, nsteps=-1): libwarpx.warpx_evolve(nsteps) - def finalize(self): + def finalize(self, finalize_mpi=1): libwarpx.warpx_finalize() + libwarpx.amrex_finalize(finalize_mpi) def getProbLo(self, direction): return libwarpx.warpx_getProbLo(direction) @@ -21,4 +58,15 @@ class WarpX(Bucket): def getProbHi(self, direction): return libwarpx.warpx_getProbHi(direction) + def write_inputs(self, filename='inputs', **kw): + argv = self.create_argv_list() + with open(filename, 'w') as ff: + + for k, v in kw.iteritems(): + ff.write('{0} = {1}\n'.format(k, v)) + + for arg in argv: + ff.write('{0}\n'.format(arg)) + warpx = WarpX('warpx') + diff --git a/Python/pywarpx/WarpXPIC.py b/Python/pywarpx/WarpXPIC.py new file mode 100644 index 000000000..77ab8464f --- /dev/null +++ b/Python/pywarpx/WarpXPIC.py @@ -0,0 +1,50 @@ +from warp.run_modes.timestepper import PICAPI +from ._libwarpx import libwarpx + +class WarpXPIC(PICAPI): + + def get_time(self): + return libwarpx.warpx_gett_new(0) + + def set_time(self, time): + for i in range(libwarpx.warpx_finestLevel()+1): + libwarpx.warpx_sett_new(i, time) + + def get_step_size(self): + libwarpx.warpx_ComputeDt() + return libwarpx.warpx_getdt(0) + + def get_step_number(self): + return libwarpx.warpx_getistep(0) + + def set_step_number(self, it): + for i in range(libwarpx.warpx_finestLevel()+1): + libwarpx.warpx_setistep(i, it) + + def push_positions(self, dt): + libwarpx.warpx_PushX(0, dt) + + def push_velocities_withE(self, dt): + libwarpx.warpx_EPushV(0, dt) + + def push_velocities_withB(self, dt): + libwarpx.warpx_BPushV(0, dt) + + def get_self_fields(self): + libwarpx.warpx_FieldGather(0) + + def calculate_source(self): + libwarpx.warpx_CurrentDeposition(0) + + def push_Efields(self, dt): + libwarpx.warpx_EvolveE(0, dt) + libwarpx.warpx_FillBoundaryE(0, True) + + def push_Bfields(self, dt): + libwarpx.warpx_EvolveB(0, dt) + libwarpx.warpx_FillBoundaryB(0, True) + + def apply_particle_boundary_conditions(self): + libwarpx.mypc_Redistribute() # Redistribute particles + libwarpx.warpx_MoveWindow() # !!! not the correct place yet + diff --git a/Python/pywarpx/__init__.py b/Python/pywarpx/__init__.py index bddbb78b7..cd6237d39 100644 --- a/Python/pywarpx/__init__.py +++ b/Python/pywarpx/__init__.py @@ -5,12 +5,14 @@ from .Geometry import geometry from .Algo import algo from .Langmuirwave import langmuirwave from .Interpolation import interpolation -from .Particles import particles +from .Particles import particles, electrons, positrons, protons from .Laser import laser -from .AMReX import AMReX #from .timestepper import TimeStepper from .PGroup import PGroup from .PGroup import PGroups +from .WarpXPIC import WarpXPIC from ._libwarpx import add_particles + +from .callbacks import * diff --git a/Python/pywarpx/callbacks.py b/Python/pywarpx/callbacks.py new file mode 100644 index 000000000..68629d164 --- /dev/null +++ b/Python/pywarpx/callbacks.py @@ -0,0 +1,481 @@ +"""call back operations +===================== + +These are the functions which allow installing user created functions so that +they are called at various places along the time step. + +For each call back type, the following three functions are defined. + - install___: Installs a function to be called at that specified time + - uninstall___: Uninstalls the function (so it won't be called anymore) + - isinstalled___: Checks if the function is installed + +These functions all take a function or instance method as an argument. Note that +if an instance method is used, an extra reference to the method's object is saved. + +The install can be done using a decorator, which has the prefix "callfrom". See example below. + +Functions can be called at the following times: + - afterinit <installafterinit>: immediately after the init is complete + - beforeEsolve <installbeforeEsolve>: before the solve for E fields + - afterEsolve <installafterEsolve>: after the solve for E fields + - beforedeposition <installbeforedeposition>: before the particle deposition (for charge and/or current) + - afterdeposition <installafterdeposition>: after particle deposition (for charge and/or current) + - beforestep <installbeforestep>: before the time step + - afterstep <installafterstep>: after the time step + - particlescraper <installparticlescraper>: just after the particle boundary conditions are applied + but before lost particles are processed + - particleloader <installparticleloader>: at the time that the standard particle loader is called + - particleinjection <installparticleinjection>: called when particle injection happens, after the position + advance and before deposition is called, allowing a user defined + particle distribution to be injected each time step + - appliedfields <installappliedfields>: allows directly specifying any fields to be applied to the particles + during the advance + +To use a decorator, the syntax is as follows. This will install the function myplots to be called after each step. + +@callfromafterstep +def myplots(): + ppzx() + +This is equivalent to the following: + +def myplots(): + ppzx() +installafterstep(myplots) + +""" +from __future__ import generators + +import types +import copy +import time +import ctypes +import sys +import numpy + +from ._libwarpx import libwarpx + +class CallbackFunctions(object): + """ + Class to handle call back function lists. + + Note that for functions passed in that are methods of a class instance, + a full reference of the instance is saved. This extra reference means + that the object will not actually deleted if the user deletes the + original reference. This is good since the user does not need to keep + the reference to the object (for example it can be created using a local + variable in a function). It may be bad if the user thinks an object was + deleted, but it actually isn't since it had (unkown to the user) + installed a method in one of the call back lists + """ + + def __init__(self,name=None,lcallonce=0): + self.funcs = [] + self.time = 0. + self.timers = {} + self.name = name + self.lcallonce = lcallonce + + def __call__(self,*args,**kw): + "Call all of the functions in the list" + tt = self.callfuncsinlist(*args,**kw) + self.time = self.time + tt + if self.lcallonce: self.funcs = [] + + def clearlist(self): + self.funcs = [] + + def __nonzero__(self): + "Returns True if functions are installed, otherwise False" + return self.hasfuncsinstalled() + + def __len__(self): + "Returns number of functions installed" + return len(self.funcs) + + def hasfuncsinstalled(self): + "Checks if there are any functions installed" + return len(self.funcs) > 0 + + def _getmethodobject(self,func): + "For call backs that are methods, returns the method's instance" + return func[0] + + def callbackfunclist(self): + "Generator returning callable functions from the list" + funclistcopy = copy.copy(self.funcs) + for f in funclistcopy: + if isinstance(f,list): + object = self._getmethodobject(f) + if object is None: + self.funcs.remove(f) + continue + result = getattr(object,f[1]) + elif isinstance(f,basestring): + import __main__ + if f in __main__.__dict__: + result = __main__.__dict__[f] + # --- If the function with the name is found, then replace the + # --- name in the list with the function. + self.funcs[self.funcs.index(f)] = result + else: + continue + else: + result = f + if not callable(result): + print("\n\nWarning: a call back was found that is not callable.") + if self.name is not None: + print("For %s"%self.name) + print("Only callable objects can be installed.") + print("It is possible that the callable's name has been overwritten") + print("by something not callable. This can happen during restart") + print("if a function name had later been used as a variable name.") + print(self.name) + if isinstance(f,basestring): + print("The name of the call back is %s"%f) + print("\n\n") + continue + yield result + + def installfuncinlist(self,f): + "Check if the specified function is installed" + if isinstance(f,types.MethodType): + # --- If the function is a method of a class instance, then save a full + # --- reference to that instance and the method name. + finstance = f.im_self + fname = f.__name__ + self.funcs.append([finstance,fname]) + elif callable(f): + # --- If a function had already been installed by name, then skip the install. + # --- This is problematic, since no warning message is given, but it is unlikely + # --- to arise under normal circumstances. + # --- The purpose of this check is to avoid redundant installation of functions + # --- during a restore from a dump file. Without the check, functions that had been + # --- installed via a decorator would be installed an extra time since the source + # --- of the function contains the decoration (which is activated when the source + # --- is exec'd). + if f.__name__ not in self.funcs: + self.funcs.append(f) + else: + self.funcs.append(f) + + def uninstallfuncinlist(self,f): + "Uninstall the specified function" + # --- An element by element search is needed + # --- f can be a function or method object, or a name (string). + # --- Note that method objects can not be removed by name. + funclistcopy = copy.copy(self.funcs) + for func in funclistcopy: + if f == func: + self.funcs.remove(f) + return + elif isinstance(func,list) and isinstance(f,types.MethodType): + object = self._getmethodobject(func) + if f.im_self is object and f.__name__ == func[1]: + self.funcs.remove(func) + return + elif isinstance(func,basestring): + if f.__name__ == func: + self.funcs.remove(func) + return + elif isinstance(f,basestring): + if isinstance(func,basestring): funcname = func + elif isinstance(func,list): funcname = None + else: funcname = func.__name__ + if f == funcname: + self.funcs.remove(func) + return + raise Exception('Warning: no such function had been installed') + + def isinstalledfuncinlist(self,f): + "Checks if the specified function is installed" + # --- An element by element search is needed + funclistcopy = copy.copy(self.funcs) + for func in funclistcopy: + if f == func: + return 1 + elif isinstance(func,list) and isinstance(f,types.MethodType): + object = self._getmethodobject(func) + if f.im_self is object and f.__name__ == func[1]: + return 1 + elif isinstance(func,basestring): + if f.__name__ == func: + return 1 + return 0 + + def callfuncsinlist(self,*args,**kw): + "Call the functions in the list" + bb = time.time() + for f in self.callbackfunclist(): + #barrier() + t1 = time.time() + f(*args,**kw) + #barrier() + t2 = time.time() + # --- For the timers, use the function (or method) name as the key. + self.timers[f.__name__] = self.timers.get(f.__name__,0.) + (t2 - t1) + aa = time.time() + return aa - bb + +#============================================================================= + +# --- Now create the actual instances. +_afterinit = CallbackFunctions('afterinit') +_beforeEsolve = CallbackFunctions('beforeEsolve') +_afterEsolve = CallbackFunctions('afterEsolve') +_beforedeposition = CallbackFunctions('beforedeposition') +_afterdeposition = CallbackFunctions('afterdeposition') +_particlescraper = CallbackFunctions('particlescraper') +_particleloader = CallbackFunctions('particleloader') +_beforestep = CallbackFunctions('beforestep') +_afterstep = CallbackFunctions('afterstep') +_afterrestart = CallbackFunctions('afterrestart',lcallonce=1) +_particleinjection = CallbackFunctions('particleinjection') +_appliedfields = CallbackFunctions('appliedfields') + +# --- Create the objects that can be called from C. +# --- Note that each of the CFUNCTYPE instances need to be saved +_CALLBACK_FUNC_0 = ctypes.CFUNCTYPE(None) +_c_afterinit = _CALLBACK_FUNC_0(_afterinit) +libwarpx.warpx_set_callback_py_afterinit(_c_afterinit) +_c_beforeEsolve = _CALLBACK_FUNC_0(_beforeEsolve) +libwarpx.warpx_set_callback_py_beforeEsolve(_c_beforeEsolve) +_c_afterEsolve = _CALLBACK_FUNC_0(_afterEsolve) +libwarpx.warpx_set_callback_py_afterEsolve(_c_afterEsolve) +_c_beforedeposition = _CALLBACK_FUNC_0(_beforedeposition) +libwarpx.warpx_set_callback_py_beforedeposition(_c_beforedeposition) +_c_afterdeposition = _CALLBACK_FUNC_0(_afterdeposition) +libwarpx.warpx_set_callback_py_afterdeposition(_c_afterdeposition) +_c_particlescraper = _CALLBACK_FUNC_0(_particlescraper) +libwarpx.warpx_set_callback_py_particlescraper(_c_particlescraper) +_c_particleloader = _CALLBACK_FUNC_0(_particleloader) +libwarpx.warpx_set_callback_py_particleloader(_c_particleloader) +_c_beforestep = _CALLBACK_FUNC_0(_beforestep) +libwarpx.warpx_set_callback_py_beforestep(_c_beforestep) +_c_afterstep = _CALLBACK_FUNC_0(_afterstep) +libwarpx.warpx_set_callback_py_afterstep(_c_afterstep) +_c_afterrestart = _CALLBACK_FUNC_0(_afterrestart) +libwarpx.warpx_set_callback_py_afterrestart(_c_afterrestart) +_c_particleinjection = _CALLBACK_FUNC_0(_particleinjection) +libwarpx.warpx_set_callback_py_particleinjection(_c_particleinjection) +_c_appliedfields = _CALLBACK_FUNC_0(_appliedfields) +libwarpx.warpx_set_callback_py_appliedfields(_c_appliedfields) + +#============================================================================= +def printcallbacktimers(tmin=1.,lminmax=False,ff=None): + """Prints timings of installed functions. + - tmin=1.: only functions with time greater than tmin will be printed + - lminmax=False: If True, prints the min and max times over all processors + - ff=None: If given, timings will be written to the file object instead of stdout + """ + if ff is None: ff = sys.stdout + for c in [_afterinit,_beforeEsolve,_afterEsolve, + _beforedeposition,_afterdeposition, + _particlescraper, + _particleloader, + _beforestep,_afterstep, + _afterrestart, + _particleinjection, + _appliedfields]: + for fname, time in c.timers.items(): + #vlist = numpy.array(gather(time)) + vlist = numpy.array([time]) + #if me > 0: continue + vsum = numpy.sum(vlist) + if vsum <= tmin: continue + vrms = numpy.sqrt(max(0.,numpy.sum(vlist**2)/len(vlist) - (numpy.sum(vlist)/len(vlist))**2)) + npes = 1. # Only works for one processor + ff.write('%20s %s %10.4f %10.4f %10.4f'%(c.name,fname,vsum,vsum/npes,vrms)) + if lminmax: + vmin = numpy.min(vlist) + vmax = numpy.max(vlist) + ff.write(' %10.4f %10.4f'%(vmin,vmax)) + it = libwarpx.warpx_getistep(0) + if it > 0: + ff.write(' %10.4f'%(vsum/npes/(it))) + ff.write('\n') + +#============================================================================= +# ---------------------------------------------------------------------------- +def callfromafterinit(f): + installafterinit(f) + return f +def installafterinit(f): + "Adds a function to the list of functions called after the init" + _afterinit.installfuncinlist(f) +def uninstallafterinit(f): + "Removes the function from the list of functions called after the init" + _afterinit.uninstallfuncinlist(f) +def isinstalledafterinit(f): + "Checks if the function is called after a init" + return _afterinit.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfrombeforeEsolve(f): + installbeforeEsolve(f) + return f +def installbeforeEsolve(f): + "Adds a function to the list of functions called before an E solve" + _beforeEsolve.installfuncinlist(f) +def uninstallbeforeEsolve(f): + "Removes the function from the list of functions called before an E solve" + _beforeEsolve.uninstallfuncinlist(f) +def isinstalledbeforeEsolve(f): + "Checks if the function is called before an E solve" + return _beforeEsolve.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromafterEsolve(f): + installafterEsolve(f) + return f +def installafterEsolve(f): + "Adds a function to the list of functions called after an E solve" + _afterEsolve.installfuncinlist(f) +def uninstallafterEsolve(f): + "Removes the function from the list of functions called after an E solve" + _afterEsolve.uninstallfuncinlist(f) +def isinstalledafterEsolve(f): + "Checks if the function is called after an E solve" + return _afterEsolve.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfrombeforedeposition(f): + installbeforedeposition(f) + return f +def installbeforedeposition(f): + "Adds a function to the list of functions called before a particle deposition" + _beforedeposition.installfuncinlist(f) +def uninstallbeforedeposition(f): + "Removes the function from the list of functions called before a particle deposition" + _beforedeposition.uninstallfuncinlist(f) +def isinstalledbeforedeposition(f): + "Checks if the function is called before a particle deposition" + return _beforedeposition.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromafterdeposition(f): + installafterdeposition(f) + return f +def installafterdeposition(f): + "Adds a function to the list of functions called after a particle deposition" + _afterdeposition.installfuncinlist(f) +def uninstallafterdeposition(f): + "Removes the function from the list of functions called after a particle deposition" + _afterdeposition.uninstallfuncinlist(f) +def isinstalledafterdeposition(f): + "Checks if the function is called after a particle deposition" + return _afterdeposition.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromparticlescraper(f): + installparticlescraper(f) + return f +def installparticlescraper(f): + "Adds a function to the list of functions called to scrape particles" + _particlescraper.installfuncinlist(f) +def uninstallparticlescraper(f): + "Removes the function from the list of functions called to scrape particles" + _particlescraper.uninstallfuncinlist(f) +def isinstalledparticlescraper(f): + "Checks if the function is called to scrape particles" + return _particlescraper.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromparticleloader(f): + installparticleloader(f) + return f +def installparticleloader(f): + "Adds a function to the list of functions called to load particles" + _particleloader.installfuncinlist(f) +def uninstallparticleloader(f): + "Removes the function from the list of functions called to load particles" + _particleloader.uninstallfuncinlist(f) +def isinstalledparticleloader(f): + "Checks if the function is called to load particles" + return _particleloader.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfrombeforestep(f): + installbeforestep(f) + return f +def installbeforestep(f): + "Adds a function to the list of functions called before a step" + _beforestep.installfuncinlist(f) +def uninstallbeforestep(f): + "Removes the function from the list of functions called before a step" + _beforestep.uninstallfuncinlist(f) +def isinstalledbeforestep(f): + "Checks if the function is called before a step" + return _beforestep.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromafterstep(f): + installafterstep(f) + return f +def installafterstep(f): + "Adds a function to the list of functions called after a step" + _afterstep.installfuncinlist(f) +def uninstallafterstep(f): + "Removes the function from the list of functions called after a step" + _afterstep.uninstallfuncinlist(f) +def isinstalledafterstep(f): + "Checks if the function is called after a step" + return _afterstep.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromafterrestart(f): + raise Exception('restart call back not implemented yet') + installafterrestart(f) + return f +def installafterrestart(f): + "Adds a function to the list of functions called immediately after a restart" + raise Exception('restart call back not implemented yet') + _afterrestart.installfuncinlist(f) +def uninstallafterrestart(f): + "Removes the function from the list of functions called immediately after a restart" + raise Exception('restart call back not implemented yet') + _afterrestart.uninstallfuncinlist(f) +def isinstalledafterrestart(f): + "Checks if the function is called immediately after a restart" + raise Exception('restart call back not implemented yet') + return _afterrestart.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromparticleinjection(f): + installparticleinjection(f) + return f +def installparticleinjection(f): + """ + Adds a user defined function that is to be called when particle + injection happens, after the position advance and before deposition is + called, allowing a user defined particle distribution to be injected + each time step""" + _particleinjection.installfuncinlist(f) +def uninstallparticleinjection(f): + "Removes the function installed by installparticleinjection" + _particleinjection.uninstallfuncinlist(f) +def isinstalledparticleinjection(f): + "Checks if the function is called when particles injection happens" + return _particleinjection.isinstalledfuncinlist(f) + +# ---------------------------------------------------------------------------- +def callfromappliedfields(f): + raise Exception('applied fields call back not implemented yet') + installappliedfields(f) + return f +def installappliedfields(f): + """ + Adds a user defined function which can specify E and B fields which are applied + to the particles during the particle advance. + """ + raise Exception('applied fields call back not implemented yet') + _appliedfields.installfuncinlist(f) +def uninstallappliedfields(f): + "Removes the function installed by installappliedfields" + raise Exception('applied fields call back not implemented yet') + _appliedfields.uninstallfuncinlist(f) +def isinstalledappliedfields(f): + "Checks if the function is called when which applies fields" + raise Exception('applied fields call back not implemented yet') + return _appliedfields.isinstalledfuncinlist(f) + diff --git a/Python/pywarpx/timestepper.py b/Python/pywarpx/timestepper.py index f72b1669f..3068cbc82 100644 --- a/Python/pywarpx/timestepper.py +++ b/Python/pywarpx/timestepper.py @@ -1,8 +1,5 @@ -import warp -from warp import top -from warp import w3d - from ._libwarpx import libwarpx +from . import callbacks class TimeStepper(object): @@ -12,6 +9,8 @@ class TimeStepper(object): def onestep(self): + callbacks._beforestep() + self.cur_time = libwarpx.warpx_gett_new(0) self.istep = libwarpx.warpx_getistep(0) @@ -33,7 +32,11 @@ class TimeStepper(object): # --- Evolve particles to p^{n+1/2} and x^{n+1} # --- Depose current, j^{n+1/2} + callbacks._particleinjection() + callbacks._particlescraper() + callbacks._beforedeposition() libwarpx.warpx_PushParticlesandDepose(self.cur_time) + callbacks._afterdeposition() libwarpx.mypc_Redistribute() # Redistribute particles @@ -43,7 +46,9 @@ class TimeStepper(object): libwarpx.warpx_SyncCurrent() libwarpx.warpx_FillBoundaryB() + callbacks._beforeEsolve() libwarpx.warpx_EvolveE(dt,0) # We now have E^{n+1} + callbacks._afterEsolve() self.istep += 1 @@ -67,318 +72,4 @@ class TimeStepper(object): if libwarpx.warpx_checkInt() > 0 and (self.istep+1)%libwarpx.warpx_plotInt() == 0 or max_time_reached: libwarpx.warpx_WriteCheckPointFile() - - - - - - - - - - - -# --- This is not used -class TimeStepperFromPICSAR(object): - - def __init__(self, package=None, solver=None, l_debug=False): - self.package = package - self.solver = solver - self.l_debug = l_debug - - def setpackagestepnumber(self, it): - if self.package is not None: - self.package.setstepnumber(it) - - def step(self, n=1, freq_print=10, lallspecl=0): - """ - This function performs a range of Particle-In-Cell iterations - - Inputs: - - n: number of iterations - - freq_print: print frequency - """ - - if (self.l_debug): print("Call step") - - for i in range(n): - if(me == 0): - if top.it%freq_print == 0: - print('it = %g time = %g'%(top.it, top.time)) - - l_first = (lallspecl or (i == 0)) - l_last = (lallspecl or (i == n-1)) - - self.onestep(l_first, l_last) - - if (self.l_debug): print("End step") - - def onestep(self, l_first, l_last): - """ - Perform a single particle-in-cell step - """ - - if (self.l_debug): print("Call onestep") - - # --- Iteration number - self.setpackagestepnumber(top.it) - - # --- call beforestep functions - if (self.l_debug): print("Call beforestep functions") - warp.callbeforestepfuncs.callfuncsinlist() - - # --- gather fields from grid to particles - if (self.l_debug): print("Call Field gathering and particle push") - - # --- push - if l_first: - if self.package is None: - # --- Standard Warp advance - for specie in warp.listofallspecies: - for pg in specie.flatten(specie.pgroups): - for js in range(pg.ns): - self.push_velocity_second_part(js, pg) - self.push_positions(js, pg) - warp.particleboundaries3d(pg, -1, False) - - else: - # --- Particle pusher - if (self.l_debug): print("Call package particle push") - pxr.pxrpush_particles_part2() - - # --- Particle boundary consitions - if (self.l_debug): print("Call package particle boundary conditions") - pxr.particle_bcs() - - if (self.l_debug): print("Call aliasparticlearrays()") - self.aliasparticlearrays() - - else: - if self.package is None: - # --- Standard Warp advance - - for specie in warp.listofallspecies: - for pg in specie.flatten(specie.pgroups): - for js in range(pg.ns): - self.push_velocity_full(js, pg) - self.push_positions(js, pg) - - warp.particleboundaries3d(pg, -1, False) - - else: - # --- Particle pusher - if (self.l_debug): print("Call package particle pusher") - pxr.field_gathering_plus_particle_pusher() - - # --- Particle boundary conditions - if (self.l_debug): print("Call package particle boundary conditions") - pxr.particle_bcs() - - self.aliasparticlearrays() - - # --- Particle sorting - if (self.l_debug): print("Call Particle Sorting") - if self.package is not None: - # --- This should be a function installed before load rho - pxr.particle_sorting_sub() - - # --- call beforeloadrho functions - if (self.l_debug): print("Call beforeloadrho functions") - warp.beforeloadrho.callfuncsinlist() - - pgroups = [] - for specie in warp.listofallspecies: - pgroups += specie.flatten(specie.pgroups) - - self.pgroups = pgroups - - # --- Call user-defined injection routines - if (self.l_debug): print("Call user-defined injection routines") - warp.userinjection.callfuncsinlist() - - xgriddiff = w3d.xmmin - pxr.xmin - ygriddiff = w3d.ymmin - pxr.ymin - zgriddiff = w3d.zmmin - pxr.zmin - - if (xgriddiff != 0 or ygriddiff != 0 or zgriddiff != 0): - pxr.pxr_move_sim_boundaries(xgriddiff, ygriddiff, zgriddiff) - - - if (self.l_debug): print("Call loadrho") - self.solver.loadrho(pgroups = pgroups) - if (self.l_debug): print("Call loadj") - self.solver.loadj(pgroups = pgroups) - - if (self.l_debug): print("Call dosolve") - self.solver.dosolve() - - if self.package is None: - for specie in warp.listofallspecies: - for pg in specie.flatten(specie.pgroups): - for js in range(pg.ns): - self.fetcheb(js, pg) - if l_last: - self.push_velocity_first_part(js, pg) - - else: - if l_last: - if (self.l_debug): print("Call package push particles 1") - pxr.pxrpush_particles_part1() - - # --- update time, time counter - top.time += top.dt - if top.it%top.nhist == 0: -# zmmnt() - minidiag(top.it, top.time, top.lspecial) - top.it += 1 - - # --- Load balance function should be installed after step - - # --- call afterstep functions - if (self.l_debug): print("Call callafterstepfuncs.callfuncsinlist()") - warp.callafterstepfuncs.callfuncsinlist() - - - # --- The following methods use the standard Warp routines - def fetcheb(self, js, pg=None): - if self.l_verbose:print(me, 'enter fetcheb') - if pg is None: - pg = top.pgroup - np = pg.nps[js] - if np == 0: return - il = pg.ins[js] - 1 - iu = il + pg.nps[js] - w3d.pgroupfsapi = pg - w3d.ipminfsapi = pg.ins[js] - w3d.npfsapi = pg.nps[js] - pg.ex[il:iu] = 0. - pg.ey[il:iu] = 0. - pg.ez[il:iu] = 0. - pg.bx[il:iu] = 0. - pg.by[il:iu] = 0. - pg.bz[il:iu] = 0. - self.fetche() - self.fetchb() - w3d.pgroupfsapi = None - - def push_velocity_full(self, js, pg=None): - if self.l_verbose:print(me, 'enter push_velocity_full') - if pg is None: - pg = top.pgroup - np = pg.nps[js] - if np == 0: return - il = pg.ins[js] - 1 - iu = il + pg.nps[js] - if pg.lebcancel_pusher: - warp.ebcancelpush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], pg.gaminv[il:iu], - pg.ex[il:iu], pg.ey[il:iu], pg.ez[il:iu], - pg.bx[il:iu], pg.by[il:iu], pg.bz[il:iu], - pg.sq[js], pg.sm[js], top.dt, 0) - else: - # --- push velocity from electric field (half step) - warp.epush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], - pg.ex[il:iu], pg.ey[il:iu], pg.ez[il:iu], - pg.sq[js], pg.sm[js], 0.5*top.dt) - # --- update gamma - self.set_gamma(js, pg) - # --- push velocity from magnetic field - warp.bpush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], pg.gaminv[il:iu], - pg.bx[il:iu], pg.by[il:iu], pg.bz[il:iu], - pg.sq[js], pg.sm[js], top.dt, top.ibpush) - # --- push velocity from electric field (half step) - warp.epush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], - pg.ex[il:iu], pg.ey[il:iu], pg.ez[il:iu], - pg.sq[js], pg.sm[js], 0.5*top.dt) - # --- update gamma - self.set_gamma(js, pg) - - if self.l_verbose:print(me, 'exit push_velocity_first_part') - - def push_velocity_first_part(self, js, pg=None): - if self.l_verbose:print(me, 'enter push_velocity_first_part') - if pg is None: - pg = top.pgroup - np = pg.nps[js] - if np == 0: return - il = pg.ins[js] - 1 - iu = il + pg.nps[js] - if pg.lebcancel_pusher: - warp.ebcancelpush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], pg.gaminv[il:iu], - pg.ex[il:iu], pg.ey[il:iu], pg.ez[il:iu], - pg.bx[il:iu], pg.by[il:iu], pg.bz[il:iu], - pg.sq[js], pg.sm[js], top.dt, 1) - else: - # --- push velocity from electric field (half step) - warp.epush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], - pg.ex[il:iu], pg.ey[il:iu], pg.ez[il:iu], - pg.sq[js], pg.sm[js], 0.5*top.dt) - # --- update gamma - self.set_gamma(js,pg) - # --- push velocity from magnetic field - warp.bpush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], pg.gaminv[il:iu], - pg.bx[il:iu], pg.by[il:iu], pg.bz[il:iu], - pg.sq[js], pg.sm[js], 0.5*top.dt, top.ibpush) - - if self.l_verbose:print(me, 'exit push_velocity_first_part') - - def push_velocity_second_part(self, js, pg=None): - if self.l_verbose:print(me, 'enter push_velocity_second_part') - if pg is None: - pg = top.pgroup - np = pg.nps[js] - if np == 0: return - il = pg.ins[js] - 1 - iu = il + pg.nps[js] - if pg.lebcancel_pusher: - warp.ebcancelpush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], pg.gaminv[il:iu], - pg.ex[il:iu], pg.ey[il:iu], pg.ez[il:iu], - pg.bx[il:iu], pg.by[il:iu], pg.bz[il:iu], - pg.sq[js], pg.sm[js], top.dt, 2) - else: - # --- push velocity from magnetic field - warp.bpush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], pg.gaminv[il:iu], - pg.bx[il:iu], pg.by[il:iu], pg.bz[il:iu], - pg.sq[js], pg.sm[js], 0.5*top.dt, top.ibpush) - # --- push velocity from electric field (half step) - warp.epush3d(np, pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], - pg.ex[il:iu], pg.ey[il:iu], pg.ez[il:iu], - pg.sq[js], pg.sm[js], 0.5*top.dt) - # --- update gamma - self.set_gamma(js, pg) - - if self.l_verbose:print(me, 'exit push_velocity_second_part') - - def set_gamma(self, js, pg=None): - if self.l_verbose:print(me, 'enter set_gamma') - if pg is None: - pg = top.pgroup - np = pg.nps[js] - if np == 0: return - il = pg.ins[js] - 1 - iu = il + pg.nps[js] - # --- update gamma - warp.gammaadv(np, pg.gaminv[il:iu], pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], top.gamadv, top.lrelativ) - - if self.l_verbose:print(me, 'exit set_gamma') - - def push_positions(self, js, pg=None): - if self.l_verbose:print(me, 'enter push_positions') - if pg is None: - pg = top.pgroup - np = pg.nps[js] - if np == 0: return - il = pg.ins[js] - 1 - iu = il + pg.nps[js] - warp.xpush3d(np, pg.xp[il:iu], pg.yp[il:iu], pg.zp[il:iu], - pg.uxp[il:iu], pg.uyp[il:iu], pg.uzp[il:iu], - pg.gaminv[il:iu], top.dt) - - if self.l_verbose:print(me, 'exit push_positions') - - - - - - - - + callbacks._afterstep() |