EDEMpy Introduction

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.

 

Setting up a Python Virtual Environment

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.

Install EDEMpy

 

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

 

 

Getting Started

Accessing the documentation

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.

 

 

Basic Syntax

 

The sections below give an overview of the syntax used in EDEMpy:

Loading a deck

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

Reading particle data

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

 

 

Reading geometry data

 

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.

 

 

Reading contact, bond, and collision data

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.

 

Binning

Creating bins

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.
 

Reading binned properties

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.

 

Writing data

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)

 

Custom Properties

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

Creating custom properties

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
)

 

Reading custom properties

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 custom properties

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.

 

Deleting custom properties

Properties are deleted using ‘deleteCustomProperty’:

 

# Can alternatively give the property ID instead of the name
deck.particleCustomProperties.deleteCustomProperty("My Property")

 

Changing properties

Renaming a property

An existing property can be renamed:

 

deck.particleCustomProperties.renameCustomPropertyIndex(
   prev_name, # Current name
   new_name # New name
)

 

Changing the order of properties

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")

 

Complete example 

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.

Intellectual Property Rights Notice | Technical Support