EDEMpy is a python library for accessing EDEM data from .h5 files (the underlying EDEM file format). It enables pre- and post-processing of EDEM data using the vast array of tools offered by the Python ecosystem. EDEMpy does not require advanced programming knowledge.
EDEMpy is installed by default with EDEM, including its dependencies: NumPy and h5py. It is also recommended to install the following tools, as these are commonly used by EDEM users and are required for some of the examples (available on the EDEM knowledge base):
The officially supported platform for EDEMpy is currently Python 3.8.10. The default Altair installation path includes the correct version installed at:
Windows: C:\Program Files\Altair\2022.1\common\python\python3.8\win64\python.exe
Linux: ~/2022.1/altair/common/python/python3.8/linux64/bin/python
It is not safe to modify the Altair Python installation by installing EDEMpy or any other packages. This could cause unexpected behavior in EDEM and other Altair software. Instead, follow the instructions below to create an independant Virtual Environment for each project.
A Python Virtual Environment is a folder containing a standalone Python installation. It is recommended to create a new Virtual Environment for each Python project, as this helps to keep track of project dependencies and makes it a lot easier to share Python projects with others.
Create a new Virtual Environment using these commands (replacing "path\to\myenv" with any desired path for the new folder):
Windows:
cd C:\Program Files\Altair\2022.1\common\python\python3.8\win64
python.exe -m venv --system-site-packages C:\path\to\myenv
Linux:
cd ~/2022.1/altair/common/python/python3.8/linux64/bin
./python -m venv --system-site-packages ~/path/to/myenv
Once the environment has been created, it must be **activated**. Activating the environment means that any further `python` commands or scripts will run inside this new version of Python. Activate the environment using these commands:
Windows (Command Prompt):
path\to\myenv\Scripts\Activate.bat
Windows (PowerShell):
path\to\myenv\Scripts\Activate.ps1
Linux:
source path\to\myenv\bin\activate
Now that the environment is active, see the next section to install EDEMpy.
To deactivate the environment at any time, use the `deactivate` command.
See the official Python documentation for more information.
The EDEM installation folder includes EDEMpy as a PIP package file ready to be installed. For the default EDEM installation path, this is located at:
Windows: C:\Program Files\Altair\2022.1\EDEM\EDEMpy\
Linux: ~/2022.1/altair/EDEM/EDEMpy/
To install, open a console window from the folder and use the following command:
python -m pip install edempy-0.2.0-py3-none-any.whl
EDEMpy's API documentation is included within the EDEM installation files. For the default installation path, this is located at:
Windows: C:\Program Files\Altair\2022.1\EDEM\EDEMpy\help\index.html
Linux: ~/2022.1/altair/EDEM/EDEMpy/help/index.html
The same information can also be accessed for individual functions directly inside a Python console via the built-in help() command. For example, to see documentation for the Deck class:
python
>>> import edempy
>>> help(edempy.Deck)
There is also a set of EDEMpy examples on the EDEM community pages.
The sections below give an overview of the syntax used in EDEMpy:
To start using EDEMpy, load in an existing deck by creating a Deck object and providing the full path to a '.dem' file. By default, decks are loaded in 'read only' mode to prevent any accidental changes. To enable editing, specify mode='w'.
from edempy import Deck # Read-only mode is the default, but can be specified with 'r' with Deck("Rock_Box_Tutorial.dem", mode='r') as deck: ... # To enable editing, use the 'w' flag instead with Deck("Rock_Box_Tutorial.dem", mode='w') as deck: # Deck objects contain information about the deck deck.creatorData deck.interactionPairs deck.timestep deck.numTimesteps deck.timestepValues
Particle information (positions, velocities etc.) for a given particle type in a given timestep is contained in a ParticleType object, obtained as follows:
# The last timestep's index is one less than the number of timesteps last_timestep = deck.numTimesteps - 1 # A particle type can be referred to either by name or by its index particle_type = "New Particle 1" particle_data = deck.timestep[last_timestep][particle_type]
This object can then be used to get particle data in the form of NumPy arrays:
positions = particle_data.getPositions() velocities = particle_data.getVelocities() ang_velocities = particle_data.getAngularVelocities() orientations = particle_data.getOrientation() scales = particle_data.getScales() ids = particle_data.getIds() count = particle_data.numParticles
Geometry information can be queried in a similar way:
# This time we query a specific timestep index timestep_index = 6 # Geometry can also be referred to either by name or by its index geom_name = "Geometry Box 1" geom = deck.timestep[timestep_index][geom_name]
Geometries are described by two lists: a list of the coordinates of each vertex, and a list of groups of vertices (expressed as indices) that form each triangle:
coords = geom.getCoords() triangles = geom.getTriangleNodes()
The following complete example creates an interactive 3D plot all of the geometries in a given timestep using Matplotlib:
import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from edempy import Deck with Deck('RockBox_Example.dem') as deck: timestep = deck.timestep[3] geometries = timestep.geometry ax = plt.axes(projection='3d') for i in range(timestep.numGeoms): coords = geometries[i].getCoords() tri = geometries[i].getTriangleNodes() ax.plot_trisurf( coords[:,0], # x-coordinates coords[:,1], # y-coordinates coords[:,2], # z-coordinates triangles=tri, alpha=0.2, color='lightgrey' ) plt.show()
Geometry objects also provide access to some other information, such as forces, torques and transformations. See the API documentation for more information.
The syntax for reading contact, bond, and collision data all very similar. The only differences are what properties are available in each case:
# Looking at the last timestep timestep = deck.timestep[deck.numTimesteps - 1] # Get contact data for particle-particle contacts surf_contacts = timestep.contact.surfSurf surf_contacts.getIds() surf_contacts.getPositions() surf_contacts.getNormalForce() surf_contacts.getNormalOverlap() surf_contacts.getTangentialForce() surf_contacts.getTangentialOverlap() # ... # The same information is available for particle-geometry contacts geom_contacts = timestep.contact.surfGeom geom_contacts.getIds() geom_contacts.getPositions() # ... # Bonds are very similar bonds = timestep.bond bonds.getIds() bonds.getPositions() # Bonds have some additional properties, however bonds.getState() bonds.getNormalTorque() bonds.getTangentialTorque() # ... # The syntax for collisions is the same as contacts, with some additional information surf_collisions = timestep.collision.surfSurf surf_collisions.getIds() surf_collisions.getPositions() surf_collisions.getNormalEnergy() surf_collisions.getFirstRadius() surf_collisions.getSecondRadius() # ... # And again for particle-geometry collisions geom_collisions = timestep.collision.surfGeom geom_collisions.getIds() geom_collisions.getPositions() # ...
Refer to the API documentation for a full list of available properties in each case.
EDEMpy offers methods for binning objects using the BoxBin and CylinderBin classes. Users can import these classes using:
from edempy import BoxBin, CylinderBin
BoxBin is defined by an origin [x, y, z] and edge lengths.
CylinderBin is defined by start and end points [x, y, z] and a radius.
Any arbitrary list of objects can be searched for inside a bin by passing the getBinnedObjects() function two arguments. For example:
boxbin = BoxBin([-0.25, 0.0, 0.0], 0.3, 1.0, 0.2) ids = deck.timestep[tStep].particle[0].getIds() pos = deck.timestep[tStep].particle[0].getPositions() binned_ids = boxbin.getBinnedObjects(ids, pos)
The getBinnedObjects() function also accepts arguments for queryType (‘max’, ‘min’, ‘average’, or ‘total’) and transform where the user can specify a 4x4 transformation matrix for translating and rotating the bin – this can be used with the getTransformMatrix() function on geometries to have bins track geometry motion.
The getBinnedProperty() function can be used to collect binned particle, contact and bond data. For example:
binnedAveVel, x, y, z = deck.timestep[tstep].particle[0].getBinnedProperty(50, 50, 50, option='velocity’, average=True)
This returns binned average velocity for particle type 0, as well as the x, y, and z position of the bins. It is useful for displaying EDEM data as a continuum, plotted using Matplotlib functions such as contour() . You can also use it to calculate custom properties, such as segregaton index.
By default the bin’s max and min coordinates match the simulation domain, but custom limits can be passed. The default option returns IDs.
EDEMpy also provides some basic functionality to modify decks, for the purposes of automated pre-processing. As explained in the Loading a deck section above, this functionality requires the mode='w' flag to be specified.
To prevent corrupted and inconsistent decks, only the last timestep in a deck can be modified. For this reason, the following methods are members of the base Deck object and no timestep needs to be specified. The only exception to this are custom properties, as explained in the Custom properties section.
Here are a few examples of the available editing functions. For a full list, see the API documentation:
deck.addFactory(geom_name, factory_name) deck.setFactoryCreationRate(factory_name, is_mass_generation, creation_rate) deck.setFactoryTotalNumber(factory_name, total_to_create) deck.createGeometry(name, material, coords, triangles) deck.createInteraction(material1_name, material2_name, restitution, static_friction, rolling_friction) deck.createLinearTranslationKinematic(geom_name, kinematic_name='') deck.deleteKinematic(geom_name, kinematic_name) deck.setKinematicStartPoint(geom_name, kinematic_name, point) deck.setKinematicStartTime(geom_name, kinematic_name, value) deck.createMaterial(name, poisson, shear_modulus, density, work_function, is_bulk_material) deck.setMaterialDensity(name, value) deck.createParticle(name, position, velocity=None, ang_velocity=None, orientation=None, scale=None) deck.createParticles(name, positions, velocities=None, ang_velocities=None, orientations=None, scales=None) deck.setParticlePosition(name, particle_id, position, clear_forces=False) deck.setParticleVelocity(name, particle_id, velocity) deck.setParticleTypeMass(name, mass) deck.setMultisphereSpherePosition(name, sphere_index, position) deck.setPolyhedralCuboidDefinition(name, width, length, height) deck.setSpheroCylinderHalfLength(name, halfLength) deck.createPhysicsModelSurfSurf(new_model_name, model_data) deck.deletePhysicsModelSurfSurf(model_name) deck.setDomainMax(position) deck.setDomainMin(position) deck.setGravity(gravity) deck.setInteractionRestitution(material1_name, material2_name, restitution)
EDEMpy permits both reading and writing of custom properties from a deck. Unlike other EDEMpy writing functionality, this is not limited to the last timestep since it is used to write post-processed information back into a completed simulation.
There are four different types of custom property:
deck.particleCustomProperties deck.geometryCustomProperties deck.simulationCustomProperties deck.contactCustomProperties
Contact custom properties are further divided into two subtypes; particle-particle contacts and particle-geometry contacts:
deck.contactCustomProperties.surfSurf deck.contactCustomProperties.surfGeom
Properties can be created with the ‘createCustomProperty()’ function:
deck.particleCustomProperties.createCustomProperty( name="Property Name", defaultValue=[0.0, 0.0, 0.0], # This would be a 3D property )
Alternatively, if ‘defaultValue’ is to have the same value repeated for each dimension, the property’s dimensionality can instead be expressed as a number:
deck.particleCustomProperties.createCustomProperty( name="Property Name", defaultValue=0.0, numElements=3, # This is still a 3D property )
A list of existing properties is available for each property type as ‘customPropertyNames’:
# Returns a list of the names of all particle custom properties deck.particleCustomProperties.customPropertyNames
The data for a custom property can be obtained as a Numpy array using the ‘getData’ function:
deck.particleCustomProperties.getData( property="Property Name", # Can alternatively use property ID tstep=0, # Timestep index particleType=0, # Must specify a particle type )
The third parameter has different meaning for each property type:
Writing data to a custom property is done using ‘setData’ instead of ‘getData’, adding a Numpy array (or list) of the desired data as an additional parameter. For example:
deck.particleCustomProperties.setData( property="Property Name", # Can alternatively use property ID tstep=0, # Timestep index particleType=0, # Must specify a particle type value=[ # Assuming 3D property and 4 particles [0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1], [0.1, 0.1, 0.1], ] )
It is important to note that, due to the multi-threaded nature of simulations, the number and ordering of particles will not be the same in each timestep.
Properties are deleted using ‘deleteCustomProperty’:
# Can alternatively give the property ID instead of the name deck.particleCustomProperties.deleteCustomProperty("My Property")
An existing property can be renamed:
deck.particleCustomProperties.renameCustomPropertyIndex( prev_name, # Current name new_name # New name )
The index of two properties can be switched:
deck.particleCustomProperties.changeCustomPropertyIndex( old_index, new_index )
To get the current index of a property, use the ‘getPropertyIndexForName()’ function:
deck.particleCustomProperties.getPropertyIndexForName("My Property")
The following code creates a particle custom property “My Property” which starts as 0.0 for each particle and increases by 1 every timestep while the particle is in the simulation:
import edempy with edempy.Deck("path_to_deck.dem") as deck: particle_type = 0 prop_name = "My Property" properties = deck.particleCustomProperties properties.createCustomProperty( prop_name, 0.0, # Default value (scalar) ) for time_id in range(1, deck.numTimesteps): # Get current values old_data = properties.getData( property=prop_name, tstep=time_id, particleType=particle_type, ) # Add 1 new_data = old_data + 1.0 # Write new values properties.setData( property=prop_name, tstep=time_id, particleType=particle_type, value=new_data )
(c) 2022 Altair Engineering Inc. All Rights Reserved. |
||