def test_3_2D_r_theta_absorbing_boundary(self): """ Check that particles are deleted correctly when they cross a 2D absorbing boundary. The particles are electrons and there's an applied Electric field """ fncName = '(' + __file__ + ') ' + sys._getframe( ).f_code.co_name + '():\n' print('\ntest: ', fncName) if os.environ.get('DISPLAY') is None: plotFlag = False else: plotFlag = self.plot_results ## Set control variables ctrl = DTcontrol_C() # Run identifier ctrl.title = "test_ParticleBoundaryConditions.py:test_3_2D_r_theta_absorbing_boundary" # Run author ctrl.author = "tph" ctrl.timeloop_count = 0 ctrl.time = 0.0 # These are fast electrons, so the timestep is small ctrl.dt = 1.0e-6 ctrl.n_timesteps = 14 ctrl.MAX_FACET_CROSS_COUNT = 100 ctrl.write_trajectory_files = False ### Particle species input # Create an instance of the DTparticleInput class pin = ParticleInput_C() # Initialize particles pin.precision = numpy.float64 pin.particle_integration_loop = 'loop-on-particles' pin.coordinate_system = 'cartesian_xy' pin.force_components = [ 'x', 'y', ] pin.force_precision = numpy.float64 pin.use_cpp_integrators = False # Use C++ code to advance particles. # Specify the particle-species properties # Define an electron species. Use this name later to initialize this species # or to create a source of this species. speciesName = 'trajelectrons' charge = -1.0 * MyPlasmaUnits_C.elem_charge mass = 1.0 * MyPlasmaUnits_C.electron_mass dynamics = 'explicit' trajelectrons_S = ParticleSpecies_C(speciesName, charge, mass, dynamics) # Add the electrons to particle input pin.particle_species = (trajelectrons_S, ) ## Make the particle storage array for all species. particle_P = Particle_C(pin, print_flag=False) ## Give the name of the .py file containing special particle data (lists of # particles, boundary conditions, source regions, etc.) userParticlesModuleName = "UserParticles_2D_e" # Import this module userParticlesModule = im_m.import_module(userParticlesModuleName) # particle_P.user_particles_module_name = userParticlesModuleName particle_P.user_particles_class = userParticlesClass = userParticlesModule.UserParticleDistributions_C ### Add a ref to a Trajectory_C object to particle_P # Create input object for trajectories trajin = TrajectoryInput_C() trajin.maxpoints = None # Set to None to get every point trajin.extra_points = 1 # Set to 1 to make sure one boundary-crossing can be # accommodated. # Specify which particle variables to save ('step' and 't' are required). This has the # form of a numpy dtype specification. format_list_base = [int] format_list = format_list_base + [numpy.float32] * 7 trajin.charged_dict = { 'names': ['step', 't', 'x', 'ux', 'y', 'uy', 'Ex', 'Ey'], 'formats': format_list } format_list = format_list_base + [numpy.float32] * 4 trajin.implicit_dict = { 'names': ['step', 't', 'x', 'ux', 'phi'], 'formats': format_list } format_list = format_list_base + [numpy.float32] * 5 trajin.neutral_dict = { 'names': ['step', 't', 'x', 'ux', 'y', 'uy'], 'formats': format_list } ### Mesh and Fields input for the particle mesh. ## The mesh input from UserMesh_y_Fields_FE2D_Module import UserMeshInput2DCirc_C ## The mesh to be created is in an existing file umi = UserMeshInput2DCirc_C() umi.mesh_file = 'mesh_quarter_circle_crossed.xml' umi.particle_boundary_file = 'mesh_quarter_circle_crossed_Pbcs.xml' # These are the boundary-name -> int pairs used to mark mesh facets: rminIndx = 1 rmaxIndx = 2 thminIndx = 4 thmaxIndx = 8 particleBoundaryDict = { 'rmin': rminIndx, 'rmax': rmaxIndx, 'thmin': thminIndx, 'thmax': thmaxIndx, } umi.particle_boundary_dict = particleBoundaryDict ## Create the particle mesh from UserMesh_y_Fields_FE2D_Module import UserMesh2DCirc_C pmesh_M = UserMesh2DCirc_C(umi, compute_dictionaries=True, compute_cpp_arrays=False, compute_tree=True, plot_flag=self.plot_mesh) # Add this to the particle object: particle_P.pmesh_M = pmesh_M ## Get the electric field from an existing file # The following value should correspond to the element degree # used in the potential from which negE was obtained phiElementDegree = 1 if phiElementDegree == 1: # For linear elements, grad(phi) is discontinuous across # elements. To represent this field, we need Discontinuous Galerkin # elements. electricFieldElementType = 'DG' else: electricFieldElementType = 'Lagrange' # Create the negative electric field directly on the particle mesh negElectricField = Field_C(particle_P.pmesh_M, element_type=electricFieldElementType, element_degree=phiElementDegree - 1, field_type='vector') file = df_m.File('negE_test_2_2D.xml') file >> negElectricField.function ### Input for initial particles (i.e., particles present at t=0) # Name the species (it should be in species_names above) speciesName = 'trajelectrons' # Check that this species has been defined above if speciesName not in particle_P.species_names: print("The species", speciesName, "has not been defined") sys.exit() initialDistributionType = 'listed' # Check that there's a function listing the particles particles printFlag = True if hasattr(userParticlesClass, speciesName): if printFlag: print(fncName + "(DnT INFO) Initial distribution for", speciesName, "is the function of that name in", userParticlesClass) # Write error message and exit if no distribution function exists else: errorMsg = fncName + "(DnT ERROR) Need to define a particle distribution function %s in UserParticle.py for species %s " % ( speciesName, speciesName) sys.exit(errorMsg) # Collect the parameters into a dictionary # For a 'listed' type, there needs to be a function with the same name as the # species in userParticlesClass trajElectronParams = { 'species_name': speciesName, 'initial_distribution_type': initialDistributionType, } # The dictionary keys are mnemonics strings for identifying each set of # initialized particles. They're not the names of particle species. initialParticlesDict = { 'initial_trajelectrons': (trajElectronParams, ), } # Add the initialized particles to the Particle_C object particle_P.initial_particles_dict = initialParticlesDict ## Particle boundary-conditions # UserParticleBoundaryFunctions_C is where the facet-crossing callback # functions are defined. userPBndFnsClass = userParticlesModule.UserParticleBoundaryFunctions_C # abbreviation # Make the particle-mesh boundary-conditions object and add it # to the particle object. spNames = particle_P.species_names pmeshBCs = ParticleMeshBoundaryConditions_C(spNames, pmesh_M, userPBndFnsClass, print_flag=False) particle_P.pmesh_bcs = pmeshBCs # The trajectory object can now be created and added to particle_P p_P = particle_P # p_P.traj_T = Trajectory_C(trajin, ctrl, p_P.explicit_species, p_P.implicit_species, p_P.neutral_species) p_P.traj_T = Trajectory_C(trajin, ctrl, p_P.charged_species, p_P.neutral_species, p_P.species_index, p_P.mass, p_P.charge) # Initialize the particles printFlags = {} for sp in p_P.species_names: printFlags[sp] = False p_P.initialize_particles(printFlags) # Get the initial cell index of each particle. p_P.compute_mesh_cell_indices() p_P.initialize_particle_integration() ### Particle loop print("Moving", p_P.get_total_particle_count(), "particles for", ctrl.n_timesteps, "timesteps") for istep in range(ctrl.n_timesteps): # print("istep", istep) if p_P.traj_T is not None: # print 'p_P.traj_T.skip:', p_P.traj_T.skip if istep % p_P.traj_T.skip == 0: p_P.record_trajectory_data(ctrl.timeloop_count, ctrl.time, neg_E_field=negElectricField) p_P.advance_charged_particles_in_E_field( ctrl, neg_E_field=negElectricField) ctrl.timeloop_count += 1 ctrl.time += ctrl.dt # Record the LAST point on the particle trajectory if p_P.traj_T is not None: p_P.record_trajectory_data(ctrl.timeloop_count, ctrl.time, neg_E_field=negElectricField) # Plot the trajectory onto the particle mesh if self.plot_results is True: mesh = p_P.pmesh_M.mesh plotTitle = os.path.basename( __file__) + ": " + sys._getframe().f_code.co_name + ": XY mesh" holdPlot = True # Set to True to stop the plot from disappearing. p_P.traj_T.plot_trajectories_on_mesh( mesh, plotTitle, hold_plot=holdPlot ) # Plots trajectory spatial coordinates on top of the particle mesh return
def test_1D_particle_source_region(self): """Set up a one-dimensional source region and display it. """ fncName = '(' + __file__ + ') ' + sys._getframe( ).f_code.co_name + '():\n' print('\ntest: ', fncName, '(' + __file__ + ')') ## Set control variables ctrl = DTcontrol_C() ctrl.title = "test_ParticleGeneration.py:test_1D_particle_source_region" ctrl.author = "tph" ctrl.timeloop_count = 0 ctrl.time = 0.0 ctrl.dt = 1.0e-6 ctrl.n_timesteps = 1 ctrl.write_trajectory_files = False pin = self.pin pin.coordinate_system = 'cartesian_x' pin.force_components = [ 'x', ] ### Particle input ## Define electron species charge = -1.0 * MyPlasmaUnits_C.elem_charge mass = 1.0 * MyPlasmaUnits_C.electron_mass dynamics = 'explicit' electrons_S = ParticleSpecies_C('electrons', charge, mass, dynamics) # Add the electrons to particle input pin.particle_species = (electrons_S, ) ## Make the particle storage array for all species. particle_P = Particle_C(pin, print_flag=True) ## Give the name of the .py file containing special particle data (lists of # particles, boundary conditions, source regions, etc.) userParticlesModuleName = "UserParticles_1D" # Import this module userParticlesModule = im_m.import_module(userParticlesModuleName) # particle_P.user_particles_module_name = userParticlesModuleName particle_P.user_particles_class = userParticlesClass = userParticlesModule.UserParticleDistributions_C ### Mesh input for a 1D mesh # Specify the mesh parameters umi1D = UserMeshInput_C() umi1D.pmin = df_m.Point(-10.0) umi1D.pmax = df_m.Point(10.0) umi1D.cells_on_side = (20, ) # Need the comma to indicate a tuple ### Create the 1D particle mesh and add to the Particle_C object pmesh1D = UserMesh_C(umi1D, compute_dictionaries=True, compute_cpp_arrays=False, compute_tree=True, plot_flag=False) particle_P.pmesh_M = pmesh1D ### Input for particle sources # For each source: # a. Define the source region # b. Provide a particle-generating function # c. Provide the species name and physical source parameters # Create numpy storage needed below driftVelocity = np_m.empty(particle_P.particle_dimension, dtype=particle_P.precision) ## 1. Hot electron source on left side of the mesh # a. Provide the species name and physical parameters # for this source speciesName = 'electrons' # Check that this species has been defined if speciesName in particle_P.species_names: charge = particle_P.charge[speciesName] mass = particle_P.mass[speciesName] else: print("The species", speciesName, "has not been defined") sys.exit() sourceDistributionType = 'functional' # Compute a physical number-density creation rate for this source region # The value should always be positive chargeDensityRate = -1.0 # The timestep interval between calls to the creation function timeStepInterval = 1 # Get number-density added per invocation numberDensity = chargeDensityRate * timeStepInterval * ctrl.dt / charge # Check for positivity if numberDensity < 0: print("Check number density for species", speciesName, "is negative. Should be positive") sys.exit() # Compute a value for thermalSpeed from the temperature in eV temperature_eV = 2.0 temp_joule = temperature_eV * MyPlasmaUnits_C.elem_charge thermalSpeed = np_m.sqrt(2.0 * temp_joule / mass) velocity_coordinate_system = 'x_y_z' # This sets the interpretation of the # following values # Set a drift velocity vdrift_1 = 1.0 vdrift_2 = 2.0 vdrift_3 = 3.0 # driftVelocity = (vdrift_1, vdrift_2, vdrift_3) driftVelocity[0] = vdrift_1 # The particle storage arrays have only a v[0] velocity component # driftVelocity[1] = vdrift_2 # driftVelocity[2] = vdrift_3 # Set desired particles-per-cell numberPerCell = 1 # Collect the parameters for this source in a dictionary hotElectronParams = { 'species_name': speciesName, 'source_distribution_type': sourceDistributionType, 'number_density': numberDensity, 'thermal_speed': thermalSpeed, 'velocity_coordinate_system': velocity_coordinate_system, 'drift_velocity': driftVelocity, 'timestep_interval': timeStepInterval, 'number_per_cell': numberPerCell } # b. Name the particle-creation function. maxwellianGenerator = particle_P.create_maxwellian_particles # c. Source-region geometry xmin = -9.0 xmax = -4.0 hotElectronsRegion = RectangularRegion_C(pmesh1D, xmin, xmax) ## 2. Background electrons over the whole mesh ## Specify one or more species to be generated in the 2nd region. # a. Provide the species name and physical parameters for this source # Note that this is the same species as in ## 1. above. It's not necessary # for each source region to have a different species name. speciesName = 'electrons' # Check that this species has been defined above if speciesName in particle_P.species_names: charge = particle_P.charge[speciesName] mass = particle_P.mass[speciesName] else: print("The species", speciesName, "has not been defined") sys.exit() sourceDistributionType = 'functional' # Compute a value for numberDensity, the physical number-density # creation rate for this source region. # The value should always be positive. chargeDensityRate = -0.1 # The timestep interval between calls to the creation function timeStepInterval = 1 # Get charge-density per invocation numberDensity = chargeDensityRate * timeStepInterval * ctrl.dt / charge # Check for positivity if numberDensity < 0: print("Check number density for species", speciesName, "is negative. Should be positive") sys.exit() # Compute a value for thermalSpeed temperature_eV = 2.0 temp_joule = temperature_eV * MyPlasmaUnits_C.elem_charge thermalSpeed = np_m.sqrt(2.0 * temp_joule / mass) velocity_coordinate_system = 'x_y_z' # This sets the interpretation of the # following values # Set a drift velocity vdrift_1 = 0.5 vdrift_2 = 1.5 vdrift_3 = 2.5 # driftVelocity = (vdrift_1, vdrift_2, vdrift_3) # The particle storage arrays have only a v[0] velocity component driftVelocity[0] = vdrift_1 # Set desired particles-per-cell numberPerCell = 10 # Collect the parameters for this source in a dictionary backgroundElectronParams = { 'species_name': speciesName, 'source_distribution_type': sourceDistributionType, 'number_density': numberDensity, 'thermal_speed': thermalSpeed, 'velocity_coordinate_system': velocity_coordinate_system, 'drift_velocity': driftVelocity, 'timestep_interval': timeStepInterval, 'number_per_cell': numberPerCell } # b. Name the first function that will generate particles in the above region: # Just use same maxwellianGenerator function as above. # c. Source geometry: in this case, it's the whole mesh: wholeMesh = WholeMesh_C(pmesh1D) ## Put all the particle source data into a dictionary # The dictionary keys are mnemonic source names. # The dictionary values are lists of tuples giving the # a. the physical source parameters. # b. particle creation function and # c. source region ## Note: the names here are source-region names, NOT species names! particleSourceDict = { 'hot_electron_source': (hotElectronParams, maxwellianGenerator, hotElectronsRegion), 'background_electron_source': (backgroundElectronParams, maxwellianGenerator, wholeMesh), } particle_P.particle_source_dict = particleSourceDict ### Create input for a particle trajectory object # Use an input object to collect initialization data for the trajectory object self.trajin = TrajectoryInput_C() self.trajin.maxpoints = None # Set to None to get every point self.trajin.extra_points = 1 # Set to 1 to make sure one boundary-crossing can be # accommodated. Set to a larger value if there are # multiple boundary reflections. self.trajin.explicit_dict = { 'names': [ 'x', 'ux', ], 'formats': [np_m.float32] * 2 } ## Create the trajectory object and attach it to the particle object. # No trajectory storage is created until particles # with TRAJECTORY_FLAG on are encountered. p_P = particle_P traj_T = Trajectory_C(self.trajin, ctrl, p_P.charged_species, p_P.neutral_species, p_P.species_index, p_P.mass, p_P.charge) particle_P.traj_T = traj_T ## Invoke the source functions and write out the particles ### Select output for particles ctrl.particle_output_file = "particleGeneration.h5part" ctrl.particle_output_interval = 1 ctrl.particle_output_attributes = ('species_index', 'x', 'ux', 'weight') # Check these values particle_P.check_particle_output_parameters(ctrl) particle_P.add_more_particles(ctrl) # Dump the particle data to a file particle_P.initialize_particle_output_file(ctrl) # Write the particle attributes particle_P.write_particles_to_file(ctrl) return
def test_1_2D_x_y_absorbing_boundary(self): """ Check that particles are deleted correctly when they cross an absorbing boundary on a 2D Cartesian mesh. The particles are neutral H. """ fncName = '(' + __file__ + ') ' + sys._getframe( ).f_code.co_name + '():\n' print('\ntest: ', fncName) if os.environ.get('DISPLAY') is None: plotFlag = False else: plotFlag = self.plot_mesh ctrl = DTcontrol_C() # Run identifier ctrl.title = "test_ParticleBoundaryConditions.py:test_1_2D_x_y_absorbing_boundary" # Run author ctrl.author = "tph" ctrl.timeloop_count = 0 ctrl.time = 0.0 ctrl.dt = 0.5 ctrl.n_timesteps = 100 ctrl.MAX_FACET_CROSS_COUNT = 100 ctrl.write_trajectory_files = False # Create an instance of the DTparticleInput class pin = ParticleInput_C() # Settings common to all species pin.precision = numpy.float64 pin.particle_integration_loop = 'loop-on-particles' pin.coordinate_system = 'cartesian_xyz' pin.force_precision = numpy.float64 pin.use_cpp_integrators = False # Use C++ code to advance particles. # Specify the particle species properties speciesName = 'neutral_H' # Use this name later to initialize this # species or to create a source of this # species. charge = 0.0 mass = 1.0 * MyPlasmaUnits_C.AMU dynamics = 'neutral' neutralH_S = ParticleSpecies_C(speciesName, charge, mass, dynamics) # Add the neutral hydrogen to particle input pin.particle_species = (neutralH_S, ) ## Make the particle storage array for all species. particle_P = Particle_C(pin, print_flag=False) # Provide the particle distributions for the above species # This could be done more like how the mesh is specified: # import UserParticles_3D as userParticlesModule # Particles have a 3D/3D phase-space even though mesh is just 2D userParticlesModuleName = 'UserParticles_3D' # Import this module infoMsg = "%s\tImporting %s" % (fncName, userParticlesModuleName) print(infoMsg) userParticlesModule = im_m.import_module(userParticlesModuleName) # particle_P.user_particles_module_name = userParticlesModuleName particle_P.user_particles_class = userParticlesClass = userParticlesModule.UserParticleDistributions_C ### Create a trajectory object and add it to particle_P # Create input object for trajectories trajin = TrajectoryInput_C() trajin.maxpoints = None # Set to None to get every point trajin.extra_points = 1 # Set to 1 to make sure one boundary-crossing can be # accommodated. # Specify which particle variables to save. This has the # form of a numpy dtype specification. name_list_base = ['step', 't'] # These are always recorded format_list_base = [int, numpy.float32 ] # Start off the format list with types for # 'step' and 't' charged_attributes = ['x', 'ux', 'y', 'uy', 'Ex', 'Ey'] format_list = format_list_base + [numpy.float32 ] * len(charged_attributes) trajin.charged_dict = { 'names': name_list_base + charged_attributes, 'formats': format_list } #implicit_attributes = ['x', 'ux', 'phi'] #format_list = format_list_base + [numpy.float32]*len(implicit_attributes) # trajin.implicit_dict = {'names': name_list_base+implicit_attributes, 'formats': format_list} neutral_attributes = ['x', 'ux', 'y', 'uy'] format_list = format_list_base + [numpy.float32 ] * len(neutral_attributes) trajin.neutral_dict = { 'names': name_list_base + neutral_attributes, 'formats': format_list } # Add a traj_T reference to the particle object p_P = particle_P # abbreviation #p_P.traj_T = Trajectory_C(trajin, ctrl, p_P.explicit_species, p_P.implicit_species, p_P.neutral_species) p_P.traj_T = Trajectory_C(trajin, ctrl, p_P.charged_species, p_P.neutral_species, p_P.species_index, p_P.mass, p_P.charge) ## Mesh input for the particle mesh, including particle boundary conditions. # Create a 2D Cartesian mesh to use for advancing the particles. The particles # themselves are given 3D coordinates. infoMsg = "%s\tImporting UserMeshInput_C from UserMesh_y_Fields_FE_XYZ_Module" % ( fncName) print(infoMsg) from UserMesh_y_Fields_FE_XYZ_Module import UserMeshInput_C # 2D mesh input umi2D = UserMeshInput_C() (xmin, ymin) = (-10.0, -10.0) (xmax, ymax) = (10.0, 10.0) umi2D.pmin = df_m.Point(xmin, ymin) umi2D.pmax = df_m.Point(xmax, ymax) umi2D.cells_on_side = (4, 2) # umi2D.diagonal = 'crossed' # This could be automated, given a list of the boundary names: ['xmin', 'xmax', ...] xminIndx = 1 xmaxIndx = 2 yminIndx = 4 ymaxIndx = 8 particleBoundaryDict = { 'xmin': xminIndx, 'xmax': xmaxIndx, 'ymin': yminIndx, 'ymax': ymaxIndx, } umi2D.particle_boundary_dict = particleBoundaryDict ## Create the 2D Cartesian mesh infoMsg = "%s\tImporting UserMesh_C from UserMesh_y_Fields_FE_XYZ_Module" % ( fncName) print(infoMsg) from UserMesh_y_Fields_FE_XYZ_Module import UserMesh_C plotTitle = os.path.basename( __file__) + ": " + sys._getframe().f_code.co_name + ": XY mesh" pmesh_M = UserMesh_C(umi2D, compute_dictionaries=True, compute_cpp_arrays=False, compute_tree=True, plot_flag=self.plot_mesh, plot_title=plotTitle) # Add this to the particle object: # p_P.pmesh_M = pmesh_M # 1. Attach the particle mesh to p_P. # 2. Attach the Python particle movers. # 3. Compute the cell-neighbors and facet-normals for the particle movers. p_P.initialize_particle_mesh(pmesh_M) ### Input for initial particles (i.e., particles present at t=0) # a. Name the species (it should be in species_names above) speciesName = 'neutral_H' # Check that this species has been defined above if speciesName not in p_P.species_names: print("The species", speciesName, "has not been defined") sys.exit() initialDistributionType = 'listed' # Check that there's a function listing the particles particles printFlag = True if hasattr(userParticlesClass, speciesName): if printFlag: print(fncName + "(DnT INFO) Initial distribution for", speciesName, "is the function of that name in", userParticlesClass) # Write error message and exit if no distribution function exists else: errorMsg = fncName + "(DnT ERROR) Need to define a particle distribution function %s in UserParticle.py for species %s " % ( speciesName, speciesName) sys.exit(errorMsg) # Collect the parameters into a dictionary # The 'listed' type will expect a function with the same name as the species. neutralHParams = { 'species_name': speciesName, 'initial_distribution_type': initialDistributionType, } # The dictionary keys are mnemonics for initialized particle distributions initialParticlesDict = { 'initial_neutral_H': (neutralHParams, ), } # Add the initialized particles to the Particle_C object p_P.initial_particles_dict = initialParticlesDict # Particle boundary-conditions # UserParticleBoundaryFunctions_C is where the facet-crossing callback # functions are defined by the user. # Make an instance of UserParticleBoundaryFunctions_C userPBndFns = userParticlesModule.UserParticleBoundaryFunctions_C # abbreviation # Make the particle-mesh boundary-conditions object and add it to the # particle object. The user has to supply the facet-crossing callback # functions in the UserParticleBoundaryFunctions_C object above. spNames = p_P.species_names pmeshBCs = ParticleMeshBoundaryConditions_C(spNames, pmesh_M, userPBndFns, print_flag=False) p_P.pmesh_bcs = pmeshBCs # Create the initial particles printFlags = {} for sp in p_P.species_names: printFlags[sp] = True p_P.initialize_particles(printFlags) # Get the initial cell index of each particle. # Should this be something the pmesh computes? No: pmesh computes the # index of a single particle. It doesn't know the particle storage # infrastructure. p_P.compute_mesh_cell_indices() p_P.initialize_particle_integration() # Advance the particles for n_timesteps print("Moving", p_P.get_total_particle_count(), "particles for", ctrl.n_timesteps, "timesteps") for istep in range(ctrl.n_timesteps): if p_P.traj_T is not None: if istep % p_P.traj_T.skip == 0: p_P.record_trajectory_data(ctrl.timeloop_count, ctrl.time) p_P.advance_neutral_particles(ctrl) ctrl.timeloop_count += 1 ctrl.time += ctrl.dt # Record the LAST point on the particle trajectory if p_P.traj_T is not None: p_P.record_trajectory_data(ctrl.timeloop_count, ctrl.time) # Plot the trajectory onto the particle mesh if self.plot_results is True: mesh = p_P.pmesh_M.mesh plotTitle = os.path.basename( __file__) + ": " + sys._getframe().f_code.co_name + ": XY mesh" holdPlot = True # Set to True to stop the plot from disappearing. p_P.traj_T.plot_trajectories_on_mesh( mesh, plotTitle, hold_plot=holdPlot ) # Plots trajectory spatial coordinates on top of the particle mesh return