ESyS Particle Tutorial
ESyS Particle Tutorial
Version 2.3.1
16 October 2014
1
Preface
This document provides an introduction to Discrete Element Method (DEM) modelling
using the ESyS-Particle Simulation Software developed by the Centre for Geoscience
Computing at The University of Queensland. The guide is intended for new users and is
written as a step-by-step tutorial on the basic principles and usage of the ESyS-Particle
software. Readers are encouraged to obtain a copy of the software and try the examples
presented here. Readers are assumed to have had some experience using Python and to
be familiar with the fundamental principles of the DEM. If you have never used Python
before, the Python Language Tutorial is an excellent starting point.
Contents
1 Introduction 7
2 Simple Models 9
2.1 A simple simulation: collision of two particles . . . . . . . . . . . . . . . . 9
2.1.1 Initialisation of an ESyS-Particle simulation . . . . . . . . . . . . . 9
2.1.2 Specification of the spatial domain . . . . . . . . . . . . . . . . . . 11
2.1.3 Particle creation and initialisation . . . . . . . . . . . . . . . . . . . 11
2.1.4 Definition of inter-particle interactions . . . . . . . . . . . . . . . . 11
2.1.5 Execution of time integration . . . . . . . . . . . . . . . . . . . . . 12
2.1.6 Running an ESyS-Particle simulation from the commandline . . . . 12
2.2 Data output during simulations . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2.1 Printing simulation data to screen . . . . . . . . . . . . . . . . . . . 14
2.2.2 Data output using the ESyS-Particle CheckPointer . . . . . . . . . 16
2.2.3 Generation of particle snapshots (via subroutine calls) . . . . . . . 18
2.2.4 A Runnable module for generating snapshots . . . . . . . . . . . . . 23
2.3 Bouncing balls: adding gravity, walls and bonded particles . . . . . . . . . 28
2.3.1 Implementation of body forces: gravity and bulk viscosity . . . . . 28
2.3.2 Implementation of infinite planar walls and particle-wall interactions 29
2.3.3 Generating a bonded lattice of particles . . . . . . . . . . . . . . . . 31
3 Advanced Models 35
3.1 Slope failure & hopper flow: variable particle sizes, friction & mesh walls . 35
3.1.1 Splash-down: collapse of a block of particles with variable sizes . . . 35
3.1.2 Sand-piles: adding frictional interactions . . . . . . . . . . . . . . . 37
3.1.3 Hopper flow: Using quarter symmetry and mesh walls . . . . . . . . 42
3.2 Uniaxial compression simulations: moving walls, model calibration and
FieldSavers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.2.1 Uniaxial compression simulations . . . . . . . . . . . . . . . . . . . 47
3.2.2 Measurement of macroscopic elastic properties . . . . . . . . . . . . 55
3.3 Post-processing and data visualisation . . . . . . . . . . . . . . . . . . . . 64
3.3.1 Interactive visualisation of simulation data . . . . . . . . . . . . . . 65
3.3.2 Calculating the number and size of rock fragments . . . . . . . . . . 67
3.3.3 Visualising cracks formed during fracture simulations . . . . . . . . 67
3.4 Annular shear cells: quasi-static 2D simulations with periodic boundaries
and servo walls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
3.4.1 Two-dimensional computations and periodic boundaries . . . . . . . 70
3.4.2 Quasi-static simulations: local damping and high densities . . . . . 72
3.4.3 Servo walls and constant stress boundary conditions . . . . . . . . . 74
3.4.4 Computation of bulk frictional properties of granular media . . . . 75
2
CONTENTS 3
4 GenGeo 79
4.1 A short introduction to GenGeo . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.1 Volumes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.2 Packers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.3 Neighbour Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.2 Particles in a box: a simple GenGeo example . . . . . . . . . . . . . . . . . 79
4.3 Particles in a box Mk II: making the the boundaries smooth(er) . . . . . . 84
4.3.1 Smoothness, particle sizes and particle numbers . . . . . . . . . . . 85
4.4 Getting serious: groups, particle tags and bond tags . . . . . . . . . . . . . 86
4.5 Grouping particles: a box of clusters . . . . . . . . . . . . . . . . . . . . . 86
4.6 Lateral thinking: hierarchical packing for complex models . . . . . . . . . . 89
4.7 If nothing else helps - the MeshVolume . . . . . . . . . . . . . . . . . . . . 89
4.8 What else is there? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.8.1 Volumes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.9 Additional ESyS-Particle resources and documentation . . . . . . . . . . . 91
2.1 A plot of particle trajectories using the text output (from bingle output.py) 15
2.2 A sequence of snapshots (from bingle vis.py) . . . . . . . . . . . . . . . 22
2.3 Trajectory of a ball bouncing under gravity with linear viscosity (from
gravity.py) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.4 A sequence of snapshots of a bouncing cube of particles (from gravity
cube.py) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5
LIST OF FIGURES 6
4.1 An image (generated with Paraview) of the box filled with particles gener-
ated by the script simple box.py described in section 4.2 . . . . . . . . . 83
4.2 Images (generated with Paraview) of a box with particles fitted to the box
faces generated by the script smooth box.py described in section 4.3. The
model in A) uses particle radii between 0.2 and 1.0, the model in B) uses
particle radii between 0.05 and 1.0. . . . . . . . . . . . . . . . . . . . . . . 85
4.3 Images (generated with Paraview) of a box with clustered particles gen-
erated by the script cluster box.py described in section 4.5. Panel A)
shows the particles colored by particle tag. To enhance the visual contrast
between neighboring clusters colors are set using particle tag modulo 10.
Panel B) shows the bonds in this model. Intra-cluster bonds (bond tag 0)
are shown in blue and tags between clusters (bond tag 1) in red. . . . . . . 89
Chapter 1
The Discrete Element Method (DEM; Cundall and Strack 1979) is a popular numerical
method for simulating the dynamics of brittle-elastic or granular materials. Materials are
represented as assemblies of spherical particles, each of which may interact with neigh-
bouring particles or other objects (such as planar walls) via simplified force-displacement
interaction laws. The numerical solution involves computing the net force acting on each
particle at a given time, then updating particle velocities and positions via an explicit
finite difference integration scheme. Depending on the application of interest, many thou-
sands (or even millions) of particles may be required and simulations may consist of up
to millions of timesteps. The heavy computational burden of the DEM relative to other
numerical methods is often the single most limiting factor determining the quality and
utility of simulation results.
ESyS-Particle is an Open Source implementation of the DEM designed for execution
on multi-core Personal Computers (PCs), clusters or supercomputers running Linux-based
operating systems. A modular, object-oriented DEM simulation engine written in C++
comprises the core of the software. Spatial domain decomposition is implemented using
a master-slave strategy with inter-process communications using the Message Passing
Interface (MPI) 1.0 standard. A verlet list neighbour search algorithm is implemented
for detecting neighbouring particles and a variety of interaction laws are implemented
for bonded or unbonded interactions between particles. Particles may have up to three
translational and three rotational degrees of freedom as well as thermal properties. An
explicit first-order finite difference time integration scheme is employed. Provision is made
for file storage of both the entire model state or specific field variables during simulations.
Since the applications for the DEM are broad and varied, ESyS-Particle provides a
simple Application Programming Interface (API) allowing users to design simulations via
scripts written in the Python programming language. For numerous applications, there
is no need to modify the C++ simulation engine or re-compile the software. The Python
API allows users to
specify the initial locations and properties of particles and walls,
define the types of interactions acting on these objects,
select the types and frequency of data output during simulations, and
perform user-defined computations at regular intervals via Runnable modules.
This Users Guide provides a tutorial-based introduction to DEM modelling using
ESyS-Particle. The focus is on usage of the Python API to design and execute DEM
7
CHAPTER 1. INTRODUCTION 8
simulations. In later chapters of the tutorial, instruction is provided on the use of post-
processing tools packaged with ESyS-Particle, as well as third-party software or libraries
for simulation construction and post-analysis. New users are encouraged to install ESyS-
Particle on a PC and work through the following chapters consecutively. Additional
features and tools are introduced gradually in the context of a number of common applic-
ations for the DEM, namely:
Simple Models
The complete code-listing for this example is provided in Appendix A.1.1, entitled
bingle.py1 . It is recommended that you work through each section below, copying the
code fragments as you go rather than simply copying the complete script. Code-fragments
are identifiable by the teletext font. Shell commands will be prepended with the $
symbol.
The first import statement loads a number of relevent classes and subroutines re-
quired for all ESyS-Particle simulations. The second statement imports the Vec3 and
BoundingBox classes. Objects of the Vec3 class are 3-component vectors, useful for spe-
cifying position, velocity or acceleration vectors in 3D. BoundingBox objects specify a
1
Bingle is slang for a minor car crash or collision. Given our simulation setup, it seemed an appro-
priate name.
9
CHAPTER 2. SIMPLE MODELS 10
rectangular region of 3D space and typically denote the spatial extents of a domain or
particle assembly.
Every ESyS-Particle simulation commences with the creation of an ESyS-Particle sim-
ulation object called LsmMpi. This object provides a means to define and run a simulation
and can be thought of as a container to which we add simulation components such as a
list of particles, walls, different types of interactions and data output components. The
following code-fragment creates a simulation object:
The first statement creates an LsmMpi object and takes two arguments. The numWorker
Processes argument specifies the number of MPI processes to use for calculations. In this
example we choose to run a serial simulation (with only one worker process). However we
could just as easily set this argument to a larger value for a MPI-parallel simulation (if you
have access to a computer with multiple processor cores/CPUs). The second argument
(mpiDimList) specifies the manner in which the domain will be divided amongst the
worker processes. The first coordinate refers to the number of subdivisions in the x-
direction whilst the second and third coordinates specify the number of subdivisions in
the y- and z-directions respectively. It is important that you set numWorkerProcesses to
be equal to the total number of subdomains specified by the mpiDimList.
The second statement (sim.initNeighbourSearch) specifies the type of particles used
in the simulation. The two most common particle types are NRotSphere and RotSphere.
sim.initNeighbourSearch also sets two parameters for the contact detection algorithm.
The gridSpacing parameter defines the size of cubic cells used to identify contacting
particles. This parameter needs to be larger than the maximum particle diameter. The
verletDist parameter determines the frequency with which the contact lists are updated.
If any particle moves a distance greater than verletDist the lists are updated. Optimal
values for these two parameters satify the inequality gridSpacing > 2 maxRadius +
verletDist. Reducing the verletDist will result in more accurate force calculations
(because new contacts will be detected earlier) but the lists will be updated more fre-
quently, which is computationally expensive. In most cases, the gridSpacing should be
set to approximately 2.5 the maximum particle radius and the verletDist should be
approximately 0.2 the minimum particle radius.
These two statements result in the construction of a suitable ESyS-Particle simulation
object called sim. The simulation object now becomes a container to which we can add
particles, walls, and various types of interactions. Before we do that, we need to specify
how many timesteps to compute during the simulation and the timestep increment (in
seconds):
sim.setNumTimeSteps(10000)
sim.setTimeStepSize(0.001)
These two statements are relatively self-explanatory. A total of 10000 timesteps will
be computed, with a time increment of 0.001 sec between each timestep. It is usually a
good idea to set the timestep increment before creating particles or interactions. In some
cases, the timestep increment is needed internally to correctly initialise interactions.
CHAPTER 2. SIMPLE MODELS 11
This statement creates a so-called InteractionGroup for our simulation object. The
particular type of interaction group created depends upon the parameter set provided as
an argument. In this case the NRotElasticPrms parameter set specifies purely elastic in-
teractions between unbonded non-rotational spheres, with an elastic stiffness of 10000 N/m.
When scaling is True (the default), the normal stiffness scales with particle size. The
name argument assigns the InteractionGroup a unique user-defined label which can be
used to extract information about particle pairs undergoing this type of interaction. At
this stage we will ignore the name, but we will return to this in a later tutorial.
Depending upon the parameter set provided as an argument to the createInterac
tionGroup subroutine, different types of interactions may be specified including body
forces (e.g. gravity), particle-wall interactions and various types of particle-particle inter-
actions. Some of these different interaction types will also be encountered in subsequent
tutorials.
sim.run()
Since ESyS-Particle is designed to run in parallel using MPI, we need this complicated
commandline. If you have a multi-processor computer, you can increase the number of
processes (np) to run the simulation in parallel. (You will also need to suitably modify
the numWorkerProcesses and mpiDimList arguments in the script.) The number after
np should always be equal to (numWorkerProcesses + 1). This is because ESyS-Particle
uses a master-slave parallelisation strategy. numWorkerProcesses specifies the number of
slaves to use but MPI must initialise one extra process to act as the master.
Whats next?
No doubt if you got this far you are somewhat disappointed that the simulation did
not appear to do anything. I can assure you that (unless you got some weird error
CHAPTER 2. SIMPLE MODELS 13
messages2 ) the simulation has correctly computed the trajectories of both particles for an
interval of 10 sec. During that time the particles collided then bounced off each other,
changing the velocities and directions of each particle. Of course all this happened only
in computer memory. We did not instruct the simulation to output any data to file or the
screen. The following tutorial will describe three different ways that data can be output
during simulations. At the end of this tutorial, you will have written a re-usable module
for creating snapshot images of the particles in motion and will know how to find out
information about individual particles during a simulation.
2
If you did receive errors, carefully compare your script with the complete code-listing in Ap-
pendix A.1.1 to identify any typographical mistakes. Also make sure you indent the code exactly as
shown here because Python is particular about indentation.
CHAPTER 2. SIMPLE MODELS 14
The use of Runnable modules is arguably the best method, even though it appears com-
plicated at first. Runnables have the advantage that they can be very easily reused for
other simulations with little or no modification. In this way a user can build a library of
helpful utilities for analysis and visualisation of simulation data. We will explain how to
prepare and import your own modules, as well as how to configure your shell environment
so that Python can find your modules easily.
N_max = sim.getNumTimeSteps()
for n in range (N_max):
sim.runTimeStep()
if (n%100==0):
particles = sim.getParticleList()
p1 = particles[0].getPosn()
p2 = particles[1].getPosn()
print n,p1,p2
sim.exit()
This time, rather than simply computing all 10000 timesteps with a single subroutine call
(sim.run()), we explicitly implement the time integration loop (via the for-statement).
The sim.runTimeStep() subroutine call instructs the simulation object to compute only
one timestep of the simulation.
CHAPTER 2. SIMPLE MODELS 15
Figure 2.1: A plot of particle trajectories using the text output (from bingle output.py)
After the timestep is completed, we check whether 100 timesteps have passed (via the
if-statement). If so, we firstly obtain a particles list from the simulation object by
calling sim.getParticleList(). particles is a Python list data structure where each
member of the list is an ESyS-Particle NRotSphere object. NRotSphere objects have a
number of subroutines that permit the user to obtain various information about individual
particles (e.g. particle position, velocity, mass, radius, etc.). For more information about
the available subroutines refer to the NRotSphere class documentation.
Having obtained the particles list, we can extract the current positions of each
particle via the particles[#].getPosn() subroutine calls. These subroutine calls return
a Vec3 vector containing the x-, y- and z-coordinates of a particles centre-of-mass. Finally
we output the timestep number (n) and the positions of both particles to screen (via the
usual Python print statement).
Save your modified script to a text file called bingle output.py, then run the simu-
lation using the following shell command:
$ mpirun -np 2 which esysparticle bingle_output.py
This shell command will run the simulation and output to screen the particle positions
every 100 timesteps. An extract of 10 lines of output looks like:
CHAPTER 2. SIMPLE MODELS 16
Re-run the simulation with the following shell command (typed on one line) to redirect
the screen output to a text file called simdata.csv:
One can then use this text file to plot the trajectories of each particle, utilising your fa-
vourite graphing software. For example, import simdata.csv into a spreadsheet program
and produce an XY plot of column two versus column one and column five versus column
one. Your plot should be similar to Figure 2.1. Note that the two trajectories do not
intersect although the particles are deflected in a manner consistent with collision. This
is because the two particles are of finite radius and they collide only at their surfaces
(whereas we are only plotting the trajectories of the centre-of-mass of the particles).
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "bingle_data",
beginTimeStep = 0,
endTimeStep = 10000,
timeStepIncr = 100
)
)
fileNamePrefix: specifying the filename prefix for all the output files to be written
during the simulation,
The example above will save simulation data every 100 timesteps, commencing at the first
timestep (timestep 0) and concluding at the last timestep (timestep 10000). Often it is
advantageous to save data for only a portion of the simulation at more regular intervals,
particularly in cases where the interesting part of the simulation commences after a period
of time (e.g. after the particles have settled under gravity).
Append the code fragment above to bingle.py and save the script as bingle chk.py.
Execute the simulation using the following command:
Upon completion of the simulation, type ls at the command prompt. Failing any
errors, you should have a listing similar to the following:
bingle_chk.py
bingle_data_t=0_0.txt
bingle_data_t=0_1.txt
bingle_data_t=10000_0.txt
bingle_data_t=10000_1.txt
bingle_data_t=1000_0.txt
bingle_data_t=1000_1.txt
bingle_data_t=100_0.txt
bingle_data_t=100_1.txt
bingle_data_t=1100_0.txt
bingle_data_t=1100_1.txt
bingle_data_t=1200_0.txt
bingle_data_t=1200_1.txt
...
You should notice there are two files generated at each designated save time: bingle da
ta t=N 0.txt and bingle data t=N 1.txt, where N denotes the timestep number when
the file was saved. The first (ending with 0.txt) is a header file containing information
about the format of the corresponding data file (ending with 1.txt). Depending upon
the type of particles used, whether or not you have mesh walls etc., the format of the
checkpoint files changes so that all relevant information about the simulation is saved at
the specified times.
Lets examine one of the output files from our simulation. Type cat bingle data
t=0 1.txt at the command prompt. The result should look like this:
2
-5 5 -5 1 0 -1 1 -5 5 -5 -5 5 -5 1 -1 1 0 0 0 0 0 0
5 5 5 1.5 0 -1 2 5 5 5 5 5 5 -1 -1 -1 0 0 0 0 0 0
0
TMIG 0
CHAPTER 2. SIMPLE MODELS 18
The first line of the file specifies the number of particles in the simulation (only 2 in
our case). The following 2 lines provide data on each of the particles, with one line per
particle. We will examine the meaning of each number on these lines in a moment. After
listing the data for each particle, the next line specifies the number of triangle mesh walls
and the last line specifies the number of triangle mesh wall interaction groups. In our
example we have no mesh walls or mesh wall interaction groups, hence the 0 on each line.
We will revisit mesh walls in a later tutorial on hopper flow simulation.
Returning to the lines providing data on each particle, for simulations using NRot
Sphere particles (as we are using here), the fields in each data line correspond to the
following:
fields 1, 2 & 3: the X, Y and Z-coordinates of the current particle position
field 4: the particle radius
field 5: the particle ID
field 6: the particle tag (more on tags later)
field 7: the particle mass (recall that we set the mass of the second particle to be
2.0)
fields 8, 9 & 10: the X, Y and Z-coordinates of the initial particle position
fields 11, 12 & 13: the X, Y and Z-coordinates of the previous particle position
fields 14, 15 & 16: the X, Y and Z-components of the particle velocity
fields 17, 18 & 19: the X, Y and Z-components of the net force acting on the particle
fields 20, 21 & 22: (used with circular or periodic boundaries) specifies the circular
shift to be added in the X, Y and Z-directions
Your first impression may be that this is a lot of information to output for each particle
when you may only be interested in, say, the velocities of each particle. The CheckPointer
is designed to be a multi-purpose data output mechanism which records every piece of
information on the current state of particles in the simulation. It is an ideal mechanism
for outputing data when you intend to perform a number of different post-processing
operations on your simulation data. The downside is that the CheckPointer can use
a lot of disk space. To circumvent this problem, FieldSavers provide a mechanism to
selectively output only certain data to disk. One needs to exercise caution when using
FieldSavers because if you forget to store important information, you need to re-run the
entire simulation. We will examine how to set up FieldSavers in a later tutorial on uniaxial
compression of elastic-brittle material.
This part of the tutorial involves implementation of a Python subroutine. If you are not
already familiar with writing subroutines in Python, I recommend you first study Chapter
4 of the Python Language Tutorial. We will modify bingle output.py for this example.
The complete code-listing is available in Appendix A.1.4 entitled bingle vis.py.
Since we will be making use of the ESyS-Particle povray module, we need to add a
third import statement to the top of our script:
ESyS-Particle currently has two visualisation modules povray and vtk. Both utilise
external libraries for rendering images of simulation data (POVray and VTK respectively).
The ESyS-Particle visualisation modules are designed to provide a common interface to
these two rendering engines (so that either package may be used with minimal changes
to your Python script). Each module has both advantages and disadvantages. povray
produces very beautiful images of particle assemblies with the possibility of rendering
particles with various textures and special effects, but it lacks tools specifically designed
for scientific visualisation of datasets (e.g. for generating isosurfaces or contours). vtk
is the opposite, providing great scientific visualisation tools and an interactive graphical
interface but lacking strong support for rendering particles nicely.
Having imported the povray module, we now implement a Python subroutine called
snapshot() designed to render an image of the particle assembly and store it as a file.
The code-fragment for implementing this subroutine is as follows:
scene = pkg.Scene()
for pp in particles:
povsphere = pkg.Sphere(pp.getPosn(), pp.getRadius())
povsphere.apply(pkg.Colors.Red)
scene.add(povsphere)
camera = scene.getCamera()
camera.setLookAt(Vec3(0,0,0))
camera.setPosn(Vec3(0,0,20))
camera.setZoom(0.1)
scene.render(
offScreen=True,
interactive=False,
fileName="snap_%.4d.png" % (index),
size=[800,600]
)
return
The first line of this code-fragment defines the name of the subroutine (snapshot) and
specifies that it accept two keyword arguments:
index a unique identifier used to specify the name of the file in which to store the
rendered image.
Within the subroutine, we firstly specify that the visualisation package (pkg) will
be povray. We could just as easily have replaced povray with vtk to use the other
renderer. Having selected the package, we then construct a Scene object via the scene
= pkg.Scene() subroutine call. Scene objects are containers for actors, a camera and
(optionally) a light-source. Actors can be any three-dimensional object we wish to see in
the Scene. When defining actors, we specify the geometrical shape of the actor, its colour
or surface texture, and its position and orientation in the Scene.
ESyS-Particle visualisation modules provide support for a number of different primitive
shapes that we can use as actors in our scene (e.g. spheres, cones, cylinders and boxes). In
this tutorial we will stick with simple spheres to represent the particles in our simulation.
To specify how our scene will look, we need to add spheres to the scene via the code-
fragment:
for pp in particles:
povsphere = pkg.Sphere(pp.getPosn(), pp.getRadius())
povsphere.apply(pkg.Colors.Red)
scene.add(povsphere)
This code-fragment loops over each particle in the list provided by the particles argu-
ment. For each particle in the list, we create a sphere via the pkg.Sphere(..) subroutine,
specify the colour of the sphere using Sphere.apply(..) then add the sphere to the scene
using scene.add(..). The pkg.Sphere(..) subroutine takes two mandatory arguments
the position of the sphere (as a Vec3 vector) and the radius of the sphere. In most
cases we can simply use the original coordinate system and sizes of particles from our
simulation.
Next we must initialise the camera we will use to take a picture of our scene. This
is an important step: if we do not initialise the camera correctly our rendered image
may be empty or we may only see a portion of the scene. Typically you will need to
experiment with the camera settings to achieve the desired result. In this case I have
selected reasonable values for visualising our bingle.py simulation. The appropriate
camera setup code-fragment is:
camera = scene.getCamera()
camera.setLookAt(Vec3(0,0,0))
camera.setPosn(Vec3(0,0,20))
camera.setZoom(0.1)
The first statement returns a pointer to our scenes camera which we call camera. Next
we specify the point in 3D space we would like to be the focus of the camera (via the
camera.setLookAt(..) subroutine). We also specify the position of the camera itself
(camera.setPosn(..)) and finally we choose the zoom factor (camera.setZoom(..)).
Changing the zoom factor is a good starting point if your rendered image is empty or
doesnt look the way you desire.
Finally, we can instruct the Scene object to create an image of the scene we have just
constructed:
scene.render(
offScreen=True,
CHAPTER 2. SIMPLE MODELS 21
interactive=False,
fileName="snap_%.4d.png" % (index),
size=[800,600]
)
This subroutine call instructs ESyS-Particle to communicate with the renderer library
(either via a temporary script or directly via a socket interface) to generate an image of
the scene. A number of optional keyword arguments may be provided to control various
aspects of the final image:
offScreen a Boolean variable that determines whether the image will appear in
a window onscreen or be rendered offscreen (in the background).
filename a text string specifying the name of the file in which to save the image.
The filename extension determines the image format. Both povray and vtk support
a large range of common image formats.
size a tuple specifying the pixel resolution of the final image. Changing the
aspect ratio of this tuple can help you render the extremities of a particle assembly
that is not 4:3 aspect ratio.
Notice that the filename argument above uses Python string formatting syntax to con-
struct a filename of the form snap 000#.png where # is the index provided as the second
argument of our snapshot subroutine. This naming convention is very handy for produ-
cing a sequence of images that can later be combined into a movie of the simulation.
In a copy of bingle output.py append the import statement and subroutine definition
above directly after the two existing import statements. Having done this, all that remains
is to replace the if-statement in the time integration loop with the following:
if (n%100==0):
particles = sim.getParticleList()
snapshot(particles=particles, index=n)
Notice that we no longer print the particle positions to screen. Instead we call our new
snapshot() subroutine providing, as arguments, the current list of particles returned by
sim.getParticleList() and the current timestep number (n) as the index for naming
the image file rendered when the subroutine is called.
Save the resulting script to a text file called bingle vis.py and run the simula-
tion from the shell using a similar command as before. All being well, your simulation
should now produce a sequence of 100 image files named snap 0000.png through to
snap 9900.png. Figure 2.2 contains a few of these snapshots from various times dur-
ing the simulation. The approach, collision and rebound of the particles is now clearly
evident.
This tutorial example illustrates only very basic visualisation. However the povray
package can be used to produce much more attractive images by using some of its more
advanced features. In the next example we will describe how to write your own module for
producing image snapshots that you can reuse for subsequent simulations with relatively
minor additions to your simulation script.
CHAPTER 2. SIMPLE MODELS 22
A user-defined Runnable is a class that inherits from an ESyS-Particle base class called
Runnable. As per any Python class, we must implement an initialisation subroutine
(self. init ()) that is called whenever we construct an instance of this class. The
initialisation subroutine prepares an instance of the class for use in a simulation. The
following code-fragment implements the class definition and initialisation subroutine:
The first line defines the name of the class POVsnaps and specifies that it inherits
from the Runnable base class. Next we encounter the definition of the init (..)
subroutine. Note that we have stipulated that this subroutine accepts two mandatory
arguments: a pointer to the simulation object to which the class is attached (sim) and
an integer (interval) specifying the number of timesteps between successive images.
The init (..) subroutine first calls the equivalent base class subroutine to do some
default initialisation of our Runnable, then stores the sim pointer and interval as data
members of the class, as well as initialising a counter (self.count) that keeps track of
the total number of images rendered. (We use this later to define the image filenames.)
A self.configure() subroutine is also called, the implementation of which is as follows:
def configure(
self,
lookAt=Vec3(0,0,0),
camPosn=Vec3(0,0,20),
zoomFactor=0.1,
imageSize=[800,600]
):
self.lookAt=lookAt
self.camPosn=camPosn
self.zoomFactor=zoomFactor
self.imageSize=imageSize
def run(self):
if ((self.sim.getTimeStep() % self.interval) == 0):
self.snapshot()
self.count += 1
CHAPTER 2. SIMPLE MODELS 25
The self.run() subroutine simply waits until self.interval timesteps have elapsed,
then calls the self.snapshot() subroutine prior to incrementing the counter. This code-
fragment is reminiscent of the code we added to the time-integration loop of bingle
vis.py. Not surprisingly, the self.snapshot() subroutine looks very similar to the
subroutine we encountered in that example:
def snapshot(self):
pkg = povray
Scene = pkg.Scene()
plist = self.sim.getParticleList()
for pp in plist:
povsphere = pkg.Sphere(pp.getPosn(), pp.getRadius())
povsphere.apply(pkg.Colors.Red)
Scene.add(povsphere)
camera = Scene.getCamera()
camera.setLookAt(self.lookAt)
camera.setPosn(self.camPosn)
camera.setZoom(self.zoomFactor)
You will notice only a few minor differences though. We now use the data members
defined in the self.configure() subroutine to initialise the camera and the image size.
By using these data members we can easily configure our snapshot Runnable for different
simulations or perhaps reconfigure the camera during a simulation if we would like, for
example, to create a fly-through animation.
This statement will instruct Python to search for a file called POVsnaps.py and import
the POVsnaps class implemented therein. Having done this, Python now knows where to
find the implementation of this class when we wish to use it.
Now scroll down your simulation script and insert the following code-fragment just
before the sim.run() statement:
The first statement creates a POVsnaps object called povcam. We have specified that
it will be attached to our simulation object (sim) and snapshots will be taken every
100 timesteps. The second statement is all-important. This statement instructs the
simulation object to add our Runnable as a PostTimeStepRunnable. In other words,
the povcam.run() subroutine will be called each timestep after the simulation object
has completed force calculations and updated particle positions and velocities. We also
have the possibility of adding a Runnable whose run() subroutine is called before force
calculations. This is a PreTimeStepRunnable.
Save your simulation script in a text file called bingle Runnable.py and run the
simulation from the shell. The simulation will output snapshots of the particle assembly
in much the same way as the bingle vis.py script in the previous example. Note that
this time we only needed to add 3 statements to our simulation script to utilise the
Runnable (as compared with bingle vis.py where we had to add approximately 19 lines
of code, including the subroutine definition). We have not only avoided tiring out our
fingers typing all those lines of code; we have created a module that can be reused in
other simulation scripts just by adding the 3 lines of code above.
$ mkdir /home/my_username/Runnable_scripts/
$ cp POVsnaps.py /home/my_username/Runnable_scripts
(Dont forget to replace the word my username with you actual username!) Having
done that you must add this directory to an environment variable called PYTHONPATH. In
the bash shell, it is done this way:
$ export PYTHONPATH=/home/my_username/Runnable_scripts/:$PYTHONPATH
If you use a different shell, consult your shell documentation or ask your local linux guru
or system administrator for help. You may wish to add the shell command above to a file
that is executed whenever you open a shell (e.g. /home/my username/.bashrc).
Once your Runnable scripts directory is in your PYTHONPATH, Python will be able
to find your module whenever it encounters an import statement like from POVsnaps
import POVsnaps. You have now successfully deployed your Runnable as a reusable
Python module. From now on, pretty snapshots of your particle simulations are only 3
lines of code away!
EXERCISE: Another very useful Runnable is one that stores the particle po-
sitions to a text file at specified intervals. Write a Runnable to achieve this
CHAPTER 2. SIMPLE MODELS 27
and store it in your Runnable scripts directory for later use (HINT: you will
only need to implement a simple self. init () subroutine and a self.run()
subroutine for this. If you are unfamiliar with writing text files in Python,
refer to Section 7.2 of the Python Language Tutorial).
ASIDE: It is worth noting at this stage that the concept of a Runnable is really
quite general. It is not only useful for data analysis and output during simulations,
but we could also write Runnable modules designed to change the positions (or
velocities) of particles (or walls) during a simulation. One such Runnable could be
written to implement a so-called servo-wall that maintains a constant pressure on
the particle assembly by opposing the repulsive force due to the particles touching
the wall. This is particularly useful in uniaxial compression simulations, the topic
of a subsequent tutorial.
Whats next?
The first two tutorials in this Guide have introduced a simple two-particle ESyS-Particle
simulation and provided tools for visualising the particles in motion. We are now well
prepared to start examining some more complicated simulations including ones with grav-
ity, walls, blocks of both bonded and unbonded particles, and differing inter-particle or
particle-wall interactions. The next tutorials will introduce these techniques in the context
of progressively more interesting examples of particle simulations including a bouncing
cube made of particles and collapse of a loosely bonded prism of random-sized particles.
Our POVsnaps Runnable will be very handy to visualise our simulation results as the
simulations increase in complexity. To use POVsnaps we will only need to add the import
and initialisation code to our scripts (and possibly change the camera parameters via the
configure() subroutine).
CHAPTER 2. SIMPLE MODELS 28
Gravitational Interactions
Gravity is implemented in ESyS-Particle via an InteractionGroup specified by a para-
meter set called GravityPrms. A typical code-fragment for implementing gravity is:
sim.createInteractionGroup(
GravityPrms(name="earth-gravity", acceleration=Vec3(0,-9.81,0))
)
GravityPrms accepts two keyword arguments. The first argument (name) is a user-defined
label for the InteractionGroup and the second is a Vec3 vector specifying the direction
and magnitude of the gravitational acceleration. In this case, we have specified that
the gravitational acceleration is 9.81 m/s/s in the negative y-direction. This is the usual
value for simulations in which the y-axis is assumed to be vertical.
Bulk Viscosity
In a great many particle simulations it is advantageous to include bulk viscosity (a damp-
ing force proportional to the instantaneous velocity of each particle acting in the direction
CHAPTER 2. SIMPLE MODELS 29
that opposes motion). A relatively large bulk viscosity may be used for quasi-static simu-
lations in which only the steady-state dynamics are of interest. A small bulk viscosity in
a driven, elastodynamic simulation will attenuate propagating stress waves and eliminate
catastrophic accumulation of kinetic energy without significantly altering the numerical
solution to the original elastodynamic problem. For our bouncing ball simulation here,
bulk viscosity is a crude proxy for air resistance.
In much the same manner as for gravity, bulk viscosity is implemented in ESyS-Particle
via an InteractionGroup. In this case, the LinDampingPrms parameter set defines the
pertinent parameters for this type of body force. A typical code-fragment is:
sim.createInteractionGroup(
LinDampingPrms(
name="linDamping",
viscosity=0.1,
maxIterations=100
)
)
Once again, a unique name is provided for the interaction group, a coefficient of viscosity
is initialised and we specify a maximum number of iterations (maxIterations) for the
iterative velocity-Verlet solver used by ESyS-Particle when viscosity is included. Typically
this last argument does not need to be very large as the iterative solver converges rapidly
in most cases (less than about 10 iterations).
Choosing an appropriate coefficient of viscosity depends upon the particular problem
to be solved. For elastodynamic problems, a small value is sufficient (viscosity < 0.05)
whereas for quasi-static problems a value as large as viscosity 0.5 might be appro-
priate. For the bouncing ball example, a viscosity = 0.1 was selected by trial-and-error
to produce sufficient damping of the balls motion over an interval of 20 sec. to simulate
damped oscillations of a bouncing ball.
IMPORTANT: All three types of wall have an active side and an inactive
side. For the case of an infinite wall, the normal vector points to the active side
of the wall. Particles impinging on a wall from the active side will bounce off the
wall. However particles impinging on a wall from the inactive side will accelerate
through the wall in an unphysical manner. Both types of mesh walls have an active
side determined by the order in which vertices are specified for line-segments or
triangles. Caution should be exercised when inserting walls to ensure they are
correctly orientated (lest you get unexpected results). We will demonstrate how to
use triangle mesh walls in the tutorial on hopper flow simuation.
CHAPTER 2. SIMPLE MODELS 30
For our bouncing ball simulation, we will implement an infinite planar wall in the
XZ-plane (i.e. normal in the positive y-direction) located at Y = 10. The appropriate
code-fragment for inserting our planar wall is the following:
sim.createWall(
name="floor",
posn=Vec3(0,-10,0),
normal=Vec3(0,1,0)
)
By now, the name argument is familiar, providing a unique label for our wall. We will
use this label in a moment. The second argument (posn) is a Vec3 vector specifying a
point lying in the plane of the wall. Finally the normal argument specifies a Vec3 normal
vector for the wall. Since this vector points in the positive y-direction, the wall lies in the
XZ-plane.
Simply inserting a wall into a simulation object is insufficient. We must also define
the type of interactions between particles and walls. There are two common types of
interactions: elastic repulsion and bonded interactions. At this stage we only consider
elastic repulsion, implemented via the following code-fragment:
sim.createInteractionGroup(
NRotElasticWallPrms(
name = "elasticWall",
wallName = "floor",
normalK = 10000.0
)
)
Figure 2.3: Trajectory of a ball bouncing under gravity with linear viscosity (from
gravity.py)
The complete code-listing for this example is provided in Appendix A, entitled gravity
cube.py. We will replace the code-fragment (in gravity.py) for creating a single particle
with the code below.
For illustrative purposes, we will use the CubicBlock in our example. The SimpleBlock
and HexagBlock are implemented in the same manner. Generation of the block of random-
sized particles is somewhat different and is covered in the next tutorial.
To begin using CubicBlock add the following line to the other import lines at the
start of the code:
The following code-fragment generates an FCC cubic block of particles and adds them
to the simulation object:
A CubicBlock object is instantiated via two arguments: dimCount and radius. dimCount
is a three-element list specifying the number of particles to insert in each axis direction
(in this case, the cube will comprise 6 particles per side). The radius of the particles
is determined by the second argument. The physical dimensions of the CubicBlock are
thus governed by the combination of the radius and the dimCount (in this case the cube
will be 6m 6m 6m).
The second command (cube.rotate(..)) rotates the cube by 30 degrees about the
Z-axis. This is done so the cube will first strike the floor on an angle rather than flat
on one face. This should make the subsequent bounces a bit more interesting! The final
command adds our cube of particles to the simulation object.
sim.createConnections(
ConnectionFinder(
maxDist = 0.005,
bondTag = 1,
pList = cube
)
)
The aim of this code is to construct a list of particle-pairs that are to be bonded together.
The ConnectionFinder is an object that searches the list of particles provided as the
pList argument (our cube in this case) looking for pairs of particles that are within a
specified distance (maxDist) of each other. The chosen distance in this case is 0.005 m.
Each pair of particles found will be added to a list and assigned a bondTag which we will
use in the next section to specify the type of interactions between bonded particles.
CHAPTER 2. SIMPLE MODELS 33
bondGrp = sim.createInteractionGroup(
NRotBondPrms(
name = "sphereBonds",
normalK = 10000.0,
breakDistance = 50.0,
tag = 1,
scaling = True
)
)
NRotBondPrms contains five parameters: a bond tag specifying which bonded particles
will undergo this interaction, a unique name for the interaction group, the elastic stiffness
(normalK) of the bonds, a boolean (scaling) to specify whether to scale the stiffness
with particle size, and a breakDistance specifying the separation distance that must be
exceeded in order to break a bond between two particles. When a particle-pair exceeds
the breakDistance the bond is removed and those two particles thereafter interact ac-
cording to the interactions specified for unbonded particle pairs. In a later tutorial we
will illustrate how to model elastic-brittle fracture of a bonded particle assembly. In this
example we have chosen a very large breakDistance in order to prevent fracture of our
cubic block when it bounces.
Modify gravity.py and replace the three lines that created the single particle with the
code-fragments above, then run the simulation. This time you should obtain snapshots of
a cube of particles bouncing off the table. Figure 2.4 provides a few snapshots from this
simulation.
It is instructive to note here that although we are using non-rotational spheres and
interactions, the cube itself does rotate during the simulation. This is because forces
between the wall and individual particles comprising the cube impart a torque to the
cube as a whole when these forces do not point in the direction of the centre-of-mass of
the entire cube. This is physically what we expect for this configuration and this fact
can be exploited to simulate rotational dynamics without using the more computationally
expensive particle-scale rotational interactions implemented in ESyS-Particle. The down-
side of this approach is that we must bond together a number of particles to represent an
individual discrete entity in the simulation, thus increasing the total number of particles
and simulation time. The decision as to which approach to use depends upon the scenario
you wish to simulate. Some simulations of rotating, interacting entities do not require
particle-scale rotational dynamics to achieve physically reasonable results.
Whats next?
In this section we learned how to add body forces (such as gravity and bulk viscosity) and
simple walls to ESyS-Particle simulations. We also demonstrated how to bond particles
together to simulate rigid bodies made out of an aggregate of spheres. The rotational
dynamics observed for a cube of particles bouncing on a table arises simply by bonding
CHAPTER 2. SIMPLE MODELS 34
the constituent particles with simple elastic bonds. The elastic bonds themselves are
translational but when particles are clustered, bulk rotational dynamics (of the particle
assembly) can be achieved.
The following tutorial builds upon the techniques learned in this section, introducing
some new techniques for simulating two slightly more complicated scenarios: collapse of
a pile of material under gravity and flow of loose granular material from a hopper or
silo. We will illustrate how to create blocks comprised of particles with variable radii,
how to implement simple frictional interactions between unbonded particles, and how
to exploit symmetries to improve simulation results without increasing the number of
particles and hence the total computation time of simulations. We will also describe
how to use triangle meshes to create walls with complicated shapes or containing holes.
A hopper flow simulation will be used to illustrate how mesh walls are incorporated in
ESyS-Particle simulations.
Chapter 3
Advanced Models
geoRandomBlock = RandomBoxPacker (
35
CHAPTER 3. ADVANCED MODELS 36
minRadius = 0.2000,
maxRadius = 0.5000,
cubicPackRadius = 2.2000,
maxInsertFails = 1000,
bBox = BoundingBox(
Vec3(-5.0000, 0.0000,-5.0000),
Vec3(5.0000, 10.0000, 5.0000)
),
circDimList = [False, False, False],
tolerance = 1.0000e-05
)
geoRandomBlock.generate()
geoRandomBlock_particles = geoRandomBlock.getSimpleSphereCollection()
sim.createParticles(geoRandomBlock_particles)
1. Insert a number of seed particles at random locations within the volume ensuring
they do not overlap.
3. Repeat step 2 until the number of failed insertion attempts reaches maxInsertFails
The tolerance parameter defines what is meant by touching in the algorithm above. If
particles overlap by less than the prescribed tolerance, they are said to be touching.
The cubicPackRadius is a parameter for setting up the neighbours table used to track
relative locations of adjacent particles. The optimal value for this is approximately 2.2
maxRadius. circDimList informs the packing algorithm of any circular (or periodic)
boundaries so particles will be fitted together along these boundaries rather than being
fitted to straight walls.
This algorithm for filling a volume with spherical particles has some distinct advant-
ages over some other methods but also some limitations. The algorithm requires no equi-
libriation simulation like some dynamical methods (e.g. expanding particle algorithms)
CHAPTER 3. ADVANCED MODELS 37
to achieve a close packing of relatively low porosity. The algorithm is also easily adap-
ted for filling quite arbitrary-shaped volumes, a feature exploited in the GenGeo add-on
library for ESyS-Particle (which we will discuss later). On the downside, the user has
little control over the final distribution of particle sizes (apart from specifying the range
of sizes). Experience as shown that for a broad range of sizes (e.g. [0.1, 1.0]) the final
particle size distribution is a power-law. For a narrow range of sizes (e.g. [0.4, 0.6]) the
size distribution is approximately a uniform random distribution.
sim.createInteractionGroup (
NRotFrictionPrms (
name = "friction",
normalK = 1000.0,
dynamicMu = 0.6,
CHAPTER 3. ADVANCED MODELS 38
Figure 3.1: A sequence of snapshots from a frictionless slope collapse simulation (from
slope fail.py)
shearK = 100.0,
scaling = True
)
)
In addition to the name, normalK and scaling parameters with which we are already
familiar, NRotFrictionPrms also requires two more parameters. The first parameter
(dynamicMu) defines the coefficient of friction for touching particles whilst the second
parameter (shearK) defines the shear stiffness at the contact point. When two particles
first touch, a shear spring is created at the contact point. Forces from surrounding particles
will cause the two particles to commence sliding past one-another with the shear spring
resisting the motion. When the shear force exceeds the normal force multiplied by the
coefficient of friction, dynamic sliding commences (as the maximum shear force is governed
by the normal force and the friction coefficient). This is a simple yet effective method to
simulate both static deformation at frictional contacts and dynamic frictional sliding.
Change your slope fail.py script and replace the NRotElasticPrms interaction
group with the code-fragment above (c.f. slope friction.py in Appendix A). Re-run
the simulation and study the snapshots carefully. You should obtain a snapshot sequence
similar to that of Figure 3.2. My guess is that you will still be a little disappointed with
the results. Although a pile begins to form in the early stages of the simulation, the pile
eventually collapses (albeit a bit later than in the first example). Whilst the inter-particle
friction has clearly had an impact, collapse of the pile is inevitable because the table is
frictionless. To maintain the pile, we require frictional interactions between the particles
and the table also. In the next example we will demonstrate a way to simulate frictional
walls. After that we will discuss how often symmetry in the problem of interest can be
CHAPTER 3. ADVANCED MODELS 39
Figure 3.2: A sequence of snapshots of slope collapse with frictional particles (from slope
friction.py)
exploited to produce more useful results without increasing the total number of particles
in a simulation.
sim.createInteractionGroup (
CHAPTER 3. ADVANCED MODELS 40
NRotBondedWallPrms (
name = "floor_bonds",
wallName = "floor",
normalK = 10000.0,
particleTag = 12321
)
)
The first three parameters should now be familiar. The fourth parameter (particleTag)
specifies the tag of particles that will undergo bonded elastic interactions with the wall
denoted by wallName. All other particles that touch the wall will undergo unbonded
elastic repulsion, just like previous examples.
In order to bond specific particles to the wall, we need to assign a specific tag to those
particles prior to inserting them into the simulation object. We can do this by individually
inserting each particle within our random cube of particles and checking as we go whether
those particles are close to the wall or not. The following code fragment achieves this:
for pp in geoRandomBlock_particles:
centre = pp.getPosn()
radius = pp.getRadius()
Y = centre[1]
if (Y < 1.1*radius):
pp.setTag(12321) # tag particles nearest to the floor
sim.createParticle(pp) # add the particle to the simulation object
Rather than simply inserting the geoRandomBlock particles directly into the simulation
object with a call to sim.createParticles(..), we read each particle in the list and
check whether its centre is within 1.1 the radius of that particle. If so, we set the tag
of the particle to 12321, denoting this particle as one to bond to the wall. Finally a call
to sim.createParticle(..) adds each particle to the simulation object.
Go ahead and modify slope friction.py to include the changes above. Remember
to replace the NRotElasticWall Interaction Group with the NRotBondedWall Interac-
tion Group. The complete code-listing for the modified script is called slope friction
floor.py in Appendix A. Having made the necessary modifications, execute the simula-
tion and record some snapshots. All being well, you should see a sequence of snapshots
like Figure 3.3. Hurray! Finally we have simulated the formation of a sandpile, albeit
only above the base of the original cube. A lot of particles still flowed away due to the
frictionless surrounding area.
Whilst one may argue this simulation is still some ways from the scenario we hoped
to simulate, we have learnt quite a bit about both sand-piles and some of the features of
ESyS-Particle in the process. The results of this simulation demonstrate that by bonding
particles to walls we can simulate a rough frictional surface quite effectively (and quite
simply). The simulation also further demonstrates that a sand-pile requires a frictional
floor in order to hold itself up. Any particles rolling down outside the base of the original
cube just slide away and cannot support the weight of particles falling down from above.
Of course, we would much prefer to be able to keep more of the particles within the sand-
pile rather than losing more than half of them from the pile. The next example achieves
this to some extent and, in the process, introduces a technique of great utility in Discrete
Element Modelling - the use of symmetry in the problem of interest to reduce the number
of particles needed to simulate a phenomenon.
CHAPTER 3. ADVANCED MODELS 41
Figure 3.3: A sequence of snapshots of slope collapse with frictional particles and a
frictional floor (from slope friction floor.py)
Figure 3.4: A sequence of snapshots of slope collapse utilising quarter symmetry (from
slope friction walls.py)
3. Insert two new NRotElasticWall Interaction Groups, one for each of the new walls.
Refer to slope friction walls.py in Appendix A if you are unsure how to do these
steps.
When completed, run your simulation for 100000 timesteps and record snapshots every
1000 timesteps. The results should be similar to those of Figure 3.4. Notice that we now
have simulated formation of a pile that is almost twice as high as the previous example
albeit we have only modelled one quarter of the sand-pile. Without increasing the number
of particles we have now simulated the formation of a pile that has a basal area effectively
four times that of the previous example. Although some particles still slide away on the
frictionless floor, we have improved the spatial resolution of our sand-pile model quite
considerably without sacrificing computation time.
In many instances exploiting symmetries in the problem at hand can greatly increase
the accuracy of results without greatly increasing computation time. However one must
be careful to avoid artifacts induced by potentially inaccurate boundary conditions along
symmetry cut-planes. In some cases other problem constraints demand that large numbers
of particles must be used. In those instances, exploiting symmetry may be the only
option. Later we will encounter another useful technique for reducing problem sizes
without greatly impacting simulation results - the use of periodic boundary conditions to
simulate models that are effectively semi-infinite in one coordinate direction.
silos encounters many problems in the real world. These include rat-holing, stalling of
flow around the exit, uneven draw profiles and unexpected segregation of finer material to
name a few. The study of granular flow is a dynamic problem requiring many timesteps
of simulation. In the previous section we learnt how to use quarter symmetry to improve
simulation performance; hopper flow is another application that benefits from the use of
symmetry. By using quarter symmetry planes around an exit point and modelling those
planes using frictionless walls we can simulate models representative of hoppers of much
larger capacity.
Triangle
3D-Nodes 6
0 0 0 -5.0 0.0 0.0
1 1 0 -5.0 0.0 5.0
2 2 0 0.0 0.0 -5.0
3 3 0 0.0 0.0 0.0
4 4 0 5.0 0.0 -5.0
5 5 0 5.0 0.0 5.0
Tri3 4
0 0 0 3 1
1 0 1 3 5
2 0 5 3 4
3 0 3 2 4
The mesh file commences with a format header Triangle; the number after 3D-Nodes
specifies how many nodal points exist in the mesh followed by the specification of locations
of these points in the format:
ID Dummy Tag X Y Z
The number after Tri3 specifies how many triangle elements exist in the mesh. Nor-
mals of triangles are considered to be pointing out of the page using an anti-clockwise
piecewise specification, regarding which see Figure 3.5. The tri-elements link the nodes
in the following manner:
ID Tag Point1 Point2 Point3
sim.readMesh(
fileName = "floorMesh.msh",
meshName = "floor_mesh_wall"
)
The variable meshName will be used later to create an interaction group between the
particles and the mesh wall, just as we do for planar walls. Lets get started by editing
our slope friction walls.py script to replace the bonded floor with a mesh wall:
1. Replace the current floor wall code with the above code fragment to import the
mesh wall instead.
2. Replace the current interaction group floor bonds with the following interaction
group for mesh walls:
sim.createInteractionGroup (
NRotElasticTriMeshPrms (
name = "floorWall_repell",
meshName = "floor_mesh_wall",
normalK = 1.0000e+04
)
)
This interaction group creates a non-rotational elastic interaction that repels particles
that make contact with the mesh wall, essentially just like a planar wall. We also
wish to constrain our hopper so that particles may only exit via the hole in the mesh
wall. We can do this by adding front and right walls to our simulation, in addition
to the left and back walls added in the previous example:
3. Add a third wall located at (X, Y, Z) = (5, 0, 0) with a normal in the negative
X-direction (i.e. n = (1, 0, 0)). This wall will be known as the right wall.
4. Add a fourth wall located at (X, Y, Z) = (0, 0, 5) with a normal in the negative
Z-direction (i.e. n = (0, 0, 1)). This wall will be known as the front wall.
5. Insert two more NRotElasticWall Interaction Groups, one for each of the new walls.
CHAPTER 3. ADVANCED MODELS 45
6. Finally we will change the tagging of the particles for visualisation purposes. Change
the tagging code fragment from the previous example to the following: code to the
fragment below will colour the particles in layers of 2m in the Y direction:
The new tagging code will tag particles with one of two different tags, in layers of
2m in the vertical Y-direction. As mentioned before, particle tags can be used for
a range of simulation-related functions. In this example we have used tagging to
identify the initial location of particles in the vertical direction. This makes it easier
for us to visualise the flow of particles later.
Congratulations, you have now made all of the changes to the code required to do a
hopper flow simulation. Run your code and you should expect something as in Figure 3.6.
If not, refer to hopper flow.py in Appendix A.
As you have seen, mesh walls are a powerful and flexible feature of ESyS-Particle that
allow complex shapes and interactions to be simulated. Discussing the model above, two
unrealistic simplifications have been used that results in hopper flow dynamics somewhat
different to that of real hoppers. Firstly, we use non-rotational particles as our granular
media. This consequence of this is that friction between particles is higher than reality;
particles are not able to roll over one another. The consequent higher bulk friction results
in more frequent rat-holing or stalling of flow around the outlet. The second simplification
is the use of a frictionless mesh wall as the base of the hopper. As with the slope collapse
example in the previous section, particles can freely slide along the base surface as there
is no shear/frictional component to the interaction. This will result in a wider flow
pattern than usual, with particles sliding towards the outlet more easily. A more realistic
simulation would include frictional interactions at the base of the hopper, perhaps by
using the bonding technique introduced in the example above.
Whats next?
This chapter has introduced a number of new features of ESyS-Particle as well as some
tricks and hints for obtaining more realistic simulation results without greatly increasing
the mathematical complexity of the model. We have seen how to create a block of variable-
sized particles at random locations. This avoids unphysical dynamics often encountered
when using particles of equal size in regular crystalline packing arrangements. Techniques
for tagging particles and bonding these tagged particles to walls, as well as introduction of
mesh walls, provide methods to simulate complex boundary conditions including frictional
walls of a variety of shapes. For problems that allow it, the use of symmetry-planes can
increase the resolution of simulation results without greatly increasing the computation
time.
CHAPTER 3. ADVANCED MODELS 46
Figure 3.6: Hopper flow using a mesh base in quarter symmetry (from hopper flow.py)
In the following tutorial we examine elastic-brittle failure of rock samples under uni-
axial compression. Simulations of uniaxial compression are particularly important in
Discrete Element Modelling, providing a way to calibrate numerical models so that the
macroscopic physical properties (such as elastic moduli and failure strength) match those
of real rocks. In order to simulate rock breakage under uniaxial loads, we will need to
implement movable walls and introduce rotational cementatious elastic-brittle bonds. We
will also examine the use of FieldSavers to selectively store simulation data such as
wall forces, total strain energy and the number of broken bonds. Particular attention
will be paid to how one may calibrate DEM simulations to achieve desired macroscopic
properties.
CHAPTER 3. ADVANCED MODELS 47
Rather than incrementally building upon previous examples, this tutorial is written stand-
alone, containing all code-fragments needed to implement uniaxial compression simula-
tions. We will also discuss how to measure macroscopic elastic properties using simulation
results.
Most of these commands should now be familiar from previous tutorials. We first import
a number of useful ESyS-Particle modules and create an instance of the LsmMpi simula-
tion object. Whilst initialising the neighbour search algorithm, we specify that particles
are of type RotSphere (unlike previous examples where we used NRotSphere particles).
Subsequently we set the number of timesteps, the timestep size and the extents of the
spatial domain. The simulation object is now initialised and ready to insert particles,
interactions, walls, etc.
Vec3(-5.0000, 0.0000,-5.0000),
Vec3(5.0000, 20.0000, 5.0000)
),
circDimList = [False, False, False],
tolerance = 1.0000e-05
)
geoRandomBlock.generate()
geoRandomBlock_particles = geoRandomBlock.getSimpleSphereCollection()
This code-fragment generates a rectangular prism of particles whose radii lie in the range
[0.4, 2.0] mm. The prism is 10 20 10 mm in size with the centre of the base at the
origin. The final command adds the prism of particles to the simulation object. For
more information about the RandomBoxPacker and the ESyS-Particle particle packing
algorithm, please refer to the previous tutorial. The last section of this code-fragment
uses a ConnectionFinder to find pairs of particles within a distance of 0.005 mm of each
other. Each particle pair is tagged with a bondTag that we will use to specify the type of
interactions between bonded particles below. We first encountered the ConnectionFinder
in gravity cube.py when we created a bonded cube of particles to bounce on the floor.
Simply adding walls to the simulation object is insufficient. We must also define in-
teractions between the walls and particles. For basic uniaxial compression simulations,
repulsive elastic interactions are sufficient. If we were interested in tensile loading, we
would need to bond the walls to particles at the base and top of the model. Bonding
walls to particles was covered in the previous tutorial. For now, the following particle-
wall interactions are all that are required:
These two code-fragments specify elastic repulsion from the bottom wall and top wall.
Each Interaction Group is assigned a unique name that can be used to selectively store
various data about interactions using FieldSavers. Elastic repulsion of particles from
walls requires specification of a single microphysical parameter, the elastic stiffness (normalK).
For this simulation we set the elastic stiffness equal to 100000 N/mm.
BrittleBeamPrms(
name="pp_bonds",
youngsModulus=100000.0,
poissonsRatio=0.25,
cohesion=100.0,
tanAngle=1.0,
tag=1
)
)
The tag parameter is used to specify which particle-pairs should be bonded together. The
bond tags were assigned by the ConnectionFinder in the previous section.
The physical interpretation of rotational bonds is that two particles are connected to
one another with a cylindrical elastic beam whose radius is the mean of the radii of the
bonded particles and whose equilibrium length is the sum of the radii of those particles.
The elasticity of bonds is determined by a microscopic Youngs modulus (youngsModulus
parameter) and a microscopic Poissons ratio (poissonsRatio parameter). It should be
emphasised that the macroscopic elastic properties of an assembly of bonded particles
does not necessarily match the microscopic elastic properties of the bonds themselves.
The topology of the bond network in a particle assembly also influences its macroscopic
elastic properties.
In order to simulate brittle failure of samples, bonds require a failure threshold criterion
(or breaking strength). In ESyS-Particle, a Mohr-Coulomb failure criterion is employed.
A bond will fail (or break) if the shear stress within the bond exceeds its shear strength
( ) given by:
= C + N tan(f ) (3.1)
where C is the cohesive strength of the bond for zero normal stress (N ) and f is the in-
ternal angle of friction of the bond. The cohesion and tanAngle parameters respectively
define the cohesive strength and friction angle of bonds.
When a bond between two particles breaks, we need to specify the type of unbon-
ded interactions the particles will experience should they come into contact. Since a
broken bond represents a fracture surface, it is appropriate to specify frictional inter-
actions between unbonded particles. The following code-fragment implements frictional
interactions between unbonded, touching particles:
name="friction",
youngsModulus=100000.0,
poissonsRatio=0.25,
dynamicMu=0.4,
staticMu=0.6
)
)
name="damping2",
viscosity=0.002,
maxIterations=50
)
)
The viscosity coefficients are chosen to be small so that damping has little effect on the
elastic response of the simulated rock sample but it is sufficient to attenuate unwanted
oscillations.
run() which moves the wall and is called once each timestep of the simulation.
Copy the code above into a text file called WallLoader.py and save it in the same dir-
ectory as the file containing the code for the uniaxial compression simulation (called
rot compress.py in Appendix A).
To use this Runnable in the uniaxial compression simulation, we must do two things:
LsmMpi = sim,
wallName = "top_wall",
vPlate = Vec3 (0.0, -0.125, 0.0),
startTime = 0,
rampTime = 50000
)
sim.addPreTimeStepRunnable (wall_loader1)
Notice that the sign of the wall velocity (vPlate) is opposite for the two walls. The top
wall will move downwards and the bottom wall upwards, both at a speed of 0.125 m/s.
Although this rate is significantly higher than that typically used in laboratory uniaxial
compression experiments, it is sufficiently small to maintain quasi-static conditions in the
simulations. The piston speeds are approximately 20000 lower than the compressional
wavespeed of the simulated rock sample. The initial acceleration of the walls from zero to
the desired speed (during the first 50000 timesteps) also helps ensure the sample is loaded
quasi-statically.
Technically the simulation object now contains all of the components needed for a
uniaxial compression simulation. The only remaining item is to instruct the simulation
to execute all timesteps. This is achieved with the following command:
If you have been following the previous tutorials, you may notice that we have not specified
how data should be output during the simulation. This is covered in detail in the next
section, where we discuss the use of FieldSavers to store only the data of specific interest
during uniaxial compression simulations.
Figure 3.8: Diagram illustrating a typical stress-strain curve and how to measure Youngs
modulus (E) and the unconfined compressive strength (UCS) from such a curve
h i
~ (t) (0) X
X ~ (b) (0) X ~ (t) (t) X
~ (b) (t) .y
Y Y (t) = h i (3.3)
~ (t) (0) X
X ~ (b) (0) .y
h i
~ (t) (t) X
X ~ (b) (t) .y
= 1 h i (3.4)
X~ (t) (0) X
~ (b) (0) .y
where y is the unit vector in the direction normal to the bottom wall.
From the two formulae above, it is evident that to compute the stressstrain curve,
we need to record the net force acting on each wall and the position of each wall at each
timestep of the simulation. The next section describes how to use FieldSavers to store
the forces and positions of walls. In the following section, we will examine how to read
the output files and construct the stressstrain curve for a simulation.
as potential energy and the number of broken bonds), and walls (such as the position of
a wall and the net force acting on the wall).
As discussed in the previous section, we need to store the wall forces and positions in
order to construct the stressstrain curve for our uniaxial compression simulations. The
following code-fragment initialises a FieldSaver to store wall positions:
wallName a list of the names of walls whose position you wish to save
fileName the name of the text file in which to store the data
fileFormat the output format (RAW SERIES creates an ASCII text file)
Storing wall forces is very similar one need only specify a fieldName of Force and
change the fileName. The following code fragment will initialise a FieldSaver to store
wall forces:
The simulation may take some time so feel free to go grab a coffee while you wait!
When the simulation is completed, two text files should have been written into the
current working directory, out Position.dat and out Force.dat. If you examine one of
these files with a text editor, the first few lines should look something like the following:
0 1.125e-10 0 0 20 0
0 4.75e-10 0 0 20 0
0 1.0875e-09 0 0 20 0
0 1.95e-09 0 0 20 0
0 3.0625e-09 0 0 20 0
0 4.425e-09 0 0 20 0
0 6.0375e-09 0 0 20 0
0 7.9e-09 0 0 20 0
0 1.00125e-08 0 0 20 0
0 1.2375e-08 0 0 20 0
The file is formatted so that, for any given timestep, the vector position (or force) of each
wall is listed one after the other on the same line. Hence, the first 3 columns are the X-,
Y- and Z-components of the position (or force) of the bottom wall and the following 3
columns are those of the top wall.
The following python script will read both files and output stress and strain at each
timestep in a format suitable for plotting using gnuplot or another XY plotting package.
The formulae used to compute stress and strain are Equations 3.2 and 3.4. Figure 3.9
shows the result for a typical uniaxial compression simulation.
posnfile = open("out_Position.dat","r")
posn = posnfile.readlines()
posnfile.close()
forcefile = open("out_Force.dat","r")
force = forcefile.readlines()
forcefile.close()
Figure 3.9: Stress-Strain curve obtained from a uniaxial compression simulation (from
rot compress.py)
To execute this script, write it to a text file called make stress strain.py, save it to
the same directory where you ran the uniaxial compression simulation, then type the
following at the command prompt:
A new text file (called stress strain.dat) will be created, the first column of which will
be strain and the second will be stress.
Now that we have a stress-strain curve for the uniaxial compression simulation, it is
a simple matter to measure the slope of the linear section to estimate Youngs modulus
and to measure the peak stress an estimate for the unconfined compressive strength.
You might like to write your own script to compute Youngs modulus and UCS directly
from stress strain.dat or modify the script above.
In the next section we will examine three more useful FieldSavers for uniaxial com-
pression simulations, namely the total kinetic energy of the particles, the total strain
energy stored in bonds and the number of broken bonds. These three fields provide in-
formation on the internal deformation of the simulated rock sample, quantities not usually
directly amenable to measurement in laboratory experiments.
examine three quantities that provide insight on the internal deformation within rock
samples subjected to uniaxial compressive loads.
The first quantity is the total kinetic energy of the particle assembly. The total kinetic
energy is simply the sum of the kinetic energy of each particle, i.e., Ek = i 21 mi vi2 . It
P
Most of the FieldSaver parameters should now be familiar. A new fileFormat called
SUM is used here. This file format stores the sum of the kinetic energy of each particle. The
output format is an ASCII text file with the total kinetic energy written once per timestep.
A number of other fields may also be output using ParticleScalarFieldSavers or
ParticleVectorFieldSavers. A list of valid fieldNames for various types of FieldSav
ers is provided in Appendix B. Other helpful information can be found on this ESyS-
Particle wiki documentation page.
The second quantity is the total strain energy stored within bonds connecting particles.
The total strain energy is simply the sum of the potential energies of the rotational bonds
connecting each particle-pair. The following code-fragment initialises an InteractionScalarFieldSaver
to store the total strain energy each timestep:
Notice that instead of providing a list of wall names, we now provide the name of the In-
teraction Group, whose data we wish to store (in this case our pp bonds interaction group
defining rotational bonds between particles). Once again we specify a SUM fileFormat
so that the total potential energy is output to an ASCII text file once per timestep.
CHAPTER 3. ADVANCED MODELS 61
Figure 3.10: Time-series of total strain energy stored in bonds during a uniaxial compres-
sion simulation (from rot compress.py)
The main difference here is the choice of fieldName count instead of potential energy.
Note that this FieldSaver will output the total number of remaining bonds. If you wish
to know how many bonds have broken you need to subtract the total number of remaining
bonds from the initial number of bonds.
Time-series of the total strain energy and the number of broken bonds are shown in
Figures 3.10 and 3.11 along with the wall force time-series (Figure 3.12) for comparison.
The total strain energy time-series closely resembles the stressstrain curve, suggesting
that total internal strain energy is proportional to macroscopic stress. This is not that
unexpected given that it is the energy stored in bonds that imparts a force on the piston
CHAPTER 3. ADVANCED MODELS 62
Figure 3.12: Time-series of net wall force during a uniaxial compression simulation (from
rot compress.py)
CHAPTER 3. ADVANCED MODELS 63
walls. The time-series of broken bonds is more interesting. Firstly, it is apparent that a
significant fraction of bonds break before the peak stress is reached. In other words, the
sample undergoes significant irreversible internal damage prior to reaching the unconfined
compressive strength. The second interesting observation is that the total number of
broken bonds after the peak stress is reached is only approximately 30% of the initial
number of bonds. The sample remains largely intact even post-peak. This is exactly
what one would expect in laboratory uniaxial compression experiments as well. These
experiments do not entirely annihilate rocks to atoms, but rather fragment the rock
samples into a large number of smaller fragments. In a later tutorial, we will discuss how
to post-process CheckPointer output files to measure, amongst other things, the sizes
and number of fragments produced during our uniaxial compression simulation.
Whats Next?
In this tutorial we introduced three useful features in ESyS-Particle simulations: the abil-
ity to move walls via a Runnable to implement external loading of models, new types of
particle-pair interactions for models involving rotational particles, and some FieldSavers,
providing another mechanism for outputing specific data about particles, walls and inter-
actions during the course of a simulation. We also discussed some of the issues related to
calibration of model parameters in DEM simulations. By this stage we have covered most
of the basic features of ESyS-Particle simulations, so you are ready to start designing and
executing your own simulations.
In the following tutorial we will encounter some post-processing tools provided with
ESyS-Particle. These tools are designed for post-simulation analysis of data stored in
CheckPointer output files to permit visualisation and analysis of data that is not easily
computed during simulations. We will demonstrate how to convert CheckPoint files into
a format suitable for interactive visualisation using popular third-party software such as
ParaView and VisIt. We will also see how to visualise the locations of broken bonds and
also the shapes of fragments produced during uniaxial compression simulations.
CHAPTER 3. ADVANCED MODELS 64
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "snapshot",
beginTimeStep = 0,
endTimeStep = 250000,
timeStepIncr = 1000
)
)
A series of CheckPoint files will now have been written to the working directory, each
beginning with the prefix snapshot. These checkpoint files provide the input to a number
of post-processing tools packed with ESyS-Particle. In this chapter, we will encounter
three of these post-processing tools, namely:
1. dump2vtk: a tool to convert CheckPoint files into a format suitable for interactive
visualisation,
3. fracextract: a tool for visualising the location of broken bonds in brittle failure
simulations.
CHAPTER 3. ADVANCED MODELS 65
Having successfully executed this command, 251 new files will be written to the directory
containing the CheckPoint files. A partial directory listing of these files is as follows:
$ ls *.vtu
vtk_snaps_0.vtu
vtk_snaps_100.vtu
vtk_snaps_101.vtu
vtk_snaps_102.vtu
vtk_snaps_103.vtu
vtk_snaps_104.vtu
vtk_snaps_105.vtu
vtk_snaps_106.vtu
vtk_snaps_107.vtu
vtk_snaps_108.vtu
vtk_snaps_109.vtu
vtk_snaps_10.vtu
[...]
These new VTK files are in a suitable format for opening from within ParaView (File |
Open..). Having opened the sequence of VTK files, you will be presented with a graphical
window similar to Figure 3.13.
scalar fields:
vector fields:
In many cases, useful information can be gained by simply colouring the bonds ac-
cording to one of the scalar fields above, or the magnitude of one of the vector fields. In
other instances it is useful to use a so-called Glyph filter to visualise, for example, the
velocity field as an assembly of arrows. Perhaps the most useful Glyph filter is the Sphere
filter. This filter inserts a sphere at the location of each particle, the radius of which can
be scaled by the particle radius. The spheres can then be coloured according to any of
the fields listed above. Examples of visualising the particle assembly and a velocity field
are provided in Figure 3.14.
ParaView also provides a group of controls on the toolbar that allows one to step
through the sequence of VTK files in temporal order. Clicking on the Play symbol will
automatically step through all the (251) snapshot files one at a time. In this manner,
the progress of a simulation may be animated. There are also a number of other more
advanced features of ParaView that are useful for visualisation of DEM simulation results.
It is recommended that one explore ParaView and its documentation to learn about these
features.
Whats Next?
In this tutorial some of the ESyS-Particle post-processing tools were introduced. One of
these tools (dump2vtk) converts CheckPointer output files into VTK files, a popular 3D
CHAPTER 3. ADVANCED MODELS 68
visualisation data format. Having converted checkpoint files to VTK format, third-party
visualisation software can be used for advanced interactive visualisation of simulation
results. Another post-processing tool (grainextract) permits analysis of the number
and size of fragments produced during brittle failure simulations. In combination with
dump2vtk, grainextract can also be used to interactively visualise the formation of frag-
ments. The final post-processing tool to be considered was fracextract which calculates
the locations and times of bond breakage events. Using this tool in combination with
dump2vtk allows fracture patterns to be visualised. The ESyS-Particle post-processing
tools are designed to permit advanced analysis and visualisation of simulation data with
little additional computational expense over and above the burden required to execute
simulations.
The following tutorial introduces another popular DEM simulation model, the annular
shear cell. Shear cells are often employed in the laboratory to measure the bulk frictional
response of sheared granular media. They are also popular for studying the fragmentation
of granular media such as occurs within silos, along conveyor belts or within earthquake
fault gouge zones. The purpose of the shear cell tutorial is to demonstrate some more
features of ESyS-Particle and DEM simulations, namely how to conduct quasi-static two-
dimensional simulations, how to implement periodic boundaries in one coordinate direc-
tion, and how to apply a constant external loading force to walls. These features are
important weapons in the arsenal of a DEM modeller, finding application for simulating
a broad range of physical phenomena.
CHAPTER 3. ADVANCED MODELS 69
Figure 3.15: Diagram of a two-dimensional annular shear cell simulation employing peri-
odic boundaries in the X-direction (from shearcell.py).
how to implement servo walls to maintain a constant external loading force (or
stress) on a particle assembly.
Figure 3.15 illustrates the DEM model we wish to construct. It consists of a two-
dimensional assembly of unbonded frictional particles of variable size. A layer of particles
top and bottom are bonded elastically to planar walls that will act as driving plates. So
as to simulate a ring of particles similar to the annulus of a shear cell, we employ periodic
boundaries in the X-direction; particles exitting the model to the right will re-enter the
model to the left and vice versa. A constant compressive force will be applied to the top
driving plate in the negative Y-direction. In addition, the bottom plate will be moved at
constant speed in the X-direction. Since annular shear cell tests are typically conducted
under quasi-static conditions, we will also utilise two numerical approximations to achieve
quasi-static conditions in the simulations, namely:
2. higher than normal particle densities to ensure particle accelerations remain relat-
ively small throughout the simulation.
The complete code-listing for the shear cell simulations may be found in Appendix A.1.17.
gridSpacing = 2.5,
verletDist = 0.5
)
ESyS-Particle also permits the use of periodic boundaries in one coordinate direction
(the X-direction). Periodic boundaries in ESyS-Particle are implemented by borrowing
the code used for managing transit of particles across parallel subdomain boundaries.
Consequently, if one wishes to employ periodic boundaries, one must ensure there are
at least two parallel subdivisions in the X-direction. In the code-fragment above, this is
achieved by setting mpiDimList=[2,1,1] (and specifying the need for two worker pro-
cesses via numWorkerProcesses=2).
It is also necessary to inform ESyS-Particle that periodic boundaries are being used
when one sets the spatial domain of the simulation. The code-fragment below illustrates
how this is done:
Having assigned the appropriate number of parallel subdivisions and correctly set
the circDimList argument of LsmMpi.setSpatialDomain(..), the simulation object is
initialised to employ periodic boundaries in the X-direction. A simple test script could
easily be constructed in which a single particle is inserted with an initial velocity in the
X-direction. As the simulation progresses the particle would simply loop around and
around the X-direction, exiting one side of the domain and re-entering the other side.
If periodic boundaries were not correctly initialised, the particle would simply disappear
from the simulation once it crossed the spatial domain boundary.
For our annular shear cell simulation, we desire a particle assembly consisting of
particles of variable size, initially at random locations. As we have seen in previous
tutorials, the RandomBoxPacker can be used for this. So that we achieve a dense initial
particle packing, the RandomBoxPacker must also be informed of the presence of periodic
boundaries. The following code-fragment illustrates:
Note that we have not yet inserted the particles into the simulation object. This is because
we wish to tag the particles carefully prior to insertion, as explained in the next section.
Having assigned a tag to each particle, the density of the particles can be set using
the following syntax:
Remember to also set the density of particles with tag=2 and tag=3 using similar sub-
routine calls.
In previous tutorials, the LinDampingPrms artificial viscosity Interaction Group has
already been encountered. By assigning a relatively large value for the viscosity para-
meter, we can use LinDampingPrms to damp inertial effects. The following code-fragment
achieves this in our shear cell simulation:
Due to the way ESyS-Particle implements damping, it is important that the specific-
ation of damping interaction groups is placed after that of all other interaction groups
in a simulation script. Before adding the code-fragment above to your script, insert the
following:
1. two walls above and below the particle assembly (at Y = 0 and Y = 10),
2. NRotFrictionPrms interactions between unbonded particles, and
3. NRotBondedWallPrms to bond appropriately tagged particles to the walls.
Once you have added these items and the LinDampingPrms interaction group to your
script, we are ready to define the boundary conditions. The next section describes how
to do this.
CHAPTER 3. ADVANCED MODELS 74
[...]
Notice that the wall is moved in the X-direction this time and that shear does not com-
mence until 30000 timesteps have elapsed. The delay commencing shear is to provide
sufficient time to apply a constant vertical stress to the model, via the top wall.
Now that we have implemented constant shear boundary conditions for the bottom
wall, we must also implement a constant vertical stress loading condition on the top wall.
In DEM simulations, constant boundary forces (or stresses) are implemented via so-called
servo walls. A servo wall is a wall that is incrementally moved a small distance in order
to maintain a constant prescribed net force acting on the wall. The net force on a wall is
a combination of the forces due to all particles interacting with the wall and any external
forces (or applied motions). Consequently, in order to compute the wall displacement
required to maintain a prescribed net force, one must have access to all the instantaneous
particle-wall forces. Of course, once the wall is moved, these particle-wall forces change, so
one must recompute the net force on the wall. Typically a simple iterative procedure can
be employed in which a wall is moved a small distance, forces are recomputed, then the
wall is moved again. In most circumstances, this iterative procedure converges rapidly
and the loop is terminated when the incremental wall displacement is smaller than a
prescribed tolerance.
Although it is technically possible to implement a servo wall algorithm from within
a Runnable, this would be computationally inefficient, requiring numerous communic-
ations between the Python API, the master process and the workers. Consequently,
ESyS-Particle provides a built-in subroutine, LsmMpi.applyForceToWall (..), that im-
plements a simple servo wall algorithm. The user need only supply the name of the
Interaction Group specifying the type of particle-wall interactions, as well as the net force
to apply to the wall. The following code-fragment illustrates how one might apply a 1N
force in the Y-direction to the top wall in our shear cell simulation:
sim.applyForceToWall (
CHAPTER 3. ADVANCED MODELS 75
interactionName = "twall_bonds",
force = Vec3 (0,1,0)
)
Note that we do not supply the name of the wall but rather the particle-wall interaction
group, twall bonds.
In order to maintain quasi-static equilibrium in the shear cell simulations, it is advant-
ageous to initially increase the applied force linearly until the desired force is achieved.
This is similar to the initial acceleration implemented in the WallLoaderRunnable. A
Runnable that achieves this, called ServoWallLoader.py, is provided in Appendix A.1.18.
Once implemented, all that remains is to import and initialise this Runnable in shearcell
.py:
[...]
Figure 3.16: Time-series of the effective bulk friction coefficient from a two-dimensional
annular shear cell simulation (from shearcell.py).
force_saver = WallVectorFieldSaverPrms(
wallName=["bottom_wall", "top_wall"],
fieldName="Force",
fileName="out_Force.dat",
fileFormat="RAW_SERIES",
YbeginTimeStep=0,
endTimeStep=100000,
timeStepIncr=1
)
sim.createFieldSaver(force_saver)
This code-fragment should be quite familiar as it is almost identical to that used in the
rot compress.py tutorial.
A time-series of the bulk friction coefficient from a shear cell simulation is provided
in Figure 3.16. In this figure we average the normal and shear forces acting on the two
walls as measures for Fn and Fs in the formula above. Note that during the initial stage
of the simulation, the bulk friction coefficient is near-zero. During this interval only
a normal force is applied to the top wall (with no shear applied to the bottom wall).
Once shear commences (after 30000 timesteps) the bulk friction coefficient rapidly rises
to approximately bulk 0.65. Thereafter, rough stable sliding of the granular material
ensues, with the bulk friction coefficient remaining near-constant.
This result is typical when shearing a granular material comprised of irrotational
spheres with a constant internal friction coefficient (here we set the internal friction coef-
ficient as 0.6). Past research has demonstrated that inhibiting rolling of DEM particles
(through the use of NRotSpheres) results in a bulk friction coefficient that increases as
CHAPTER 3. ADVANCED MODELS 77
Figure 3.17: Time-series of the top wall position from a two-dimensional annular shear
cell simulation (from shearcell.py).
the internal friction coefficient increases. However, when rotational, unbonded particles
are used, the bulk friction coefficient is significantly smaller, and almost independent of
the choice of internal friction coefficient.
The reason for this is that spheres simply roll over one another without any geometrical
interlocking and, more importantly, without dilation of the granular material as it shears.
A time-series of the top wall position (as shown in Figure 3.17) confirms that the top wall
monotonically reduces in height throughout the simulation. If geometrical interlocking
had occurred, one would expect to see evidence for the top wall riding up as the granular
material becomes locally interlocked. Thankfully there is a relatively simple way to achieve
realistic frictional response in DEM shear cell simulations: utilise clusters of particles
bonded together that interlock by virtue of the surface roughness of the clusters. In the
next chapter we will discuss how one might construct a shear cell model incorporating
clusters of bonded particles representing the granular material.
Whats Next?
In this chapter, a number of useful DEM modelling techniques were introduced using annu-
lar shear cell experiments as a motivation. We discussed how to conduct two-dimensional
simulations with ESyS-Particle, including periodic boundaries in one coordinate direction.
The use of large viscosity and high particle densities was demonstrated as an effective
method to achieve quasi-static conditions in DEM simulations without altering the time-
integration scheme. Servo walls were also discussed as a mechanism to apply constant
boundary forces (or stresses) to DEM models.
In the final section of this chapter, we analysed some results from the simple shear
cell model. These results, although promising, demonstrated a lack of geometrical in-
terlocking, an important mechanism governing the frictional response of granular media.
Some DEM practitioners have implemented more complicated particle-pair interactions
to overcome this limitation. This is often a reasonable compromise when computational
resources are limited. Another approach is simply to make the model geometrically more
complex without increasing the mathematical complexity of the particle-pair interactions.
Past research using ESyS-Particle has demonstrated that the use of bonded clusters of
particles to represent individual grains results in bulk frictional response matching that
CHAPTER 3. ADVANCED MODELS 78
a Packer to place the particles into the volumes according to given criteria
the Neighbour Table - a container to store the particles and to keep track of their
relative positions and neighbour relations
4.1.1 Volumes
4.1.2 Packers
While the library is designed to be flexible and to allow many different packing methods
to be included the only packers implemented at the moment are all based to the insertion-
based algorithm described in (REF). The advantages of this packing strategy are that it
produces relatively dense particle arrangements where each interior particle is touched by
at least 4 other particles in 3D (by 3 other particles in 2D) and that there are no frozen-in
stresses between the particles. The disadvantage is that the user can not control the
particle size distribution except for the minimum and maximum particle radius allowed.
79
CHAPTER 4. GENGEO 80
in Appendix A.2.1. N.B. To make the meaning of the function arguments clear the
named argument style of python function calls will be used in the sections explaining
the GenGeo example. However, to improve general readability the compact form, i.e.
without argument names, will be used for the full listings in the appendix.
Like most simple GenGeo scripts this example consists of 5 steps:
4. running the packer to fill the box and, if required, bond the particles together
As usual, the thing to do in a python script is to import the necessary modules, therefore
the script to pack particles into a box starts with
which of course assumes that the GenGeo library 1 is in the python path. In order to
make the code more readable we first define a couple of parameters which will be needed
repeatedly throughout the script, namely the dimensions of the box we intent to fill and
the minimum and maximum radius of the particles. For simplicity we place one corner of
the box at (0.0,0.0,0.0) so we only need to specify the x-, y- and z- extent of the box to
define it fully 2 . So the next section of the script looks like this:
# box dimensions
xdim=10
ydim=20
zdim=10
This will set the shape of the box to a square prism two times as long as wide. This
shape is quite often used, for example in simulations of triaxial deformation tests. With
the parameters given here the resulting model will be relatively small ( 4000 particles)
and should build in about 10 - 20 seconds on current PC 3 . For convenience we also define
the two opposing corners of the box as vectors. N.B. In GenGeo the class for a 3D vector
is called Vector3, not Vec3 as in ESyS-Particle! The reason for this difference is to avoid
name conflicts when using both GenGeo and ESyS-Particle libraries in the same Python
script. So the definition of the corner points is
# corner points
minPoint = Vector3(0.0,0.0,0.0)
maxPoint = Vector3(xdim,ydim,zdim)
1
Despite the fact that it is spelled GenGeo throughout this tutorial, the actual module name is all
lower case gengeo, i.e. on Unix systems the shared library name is gengeo.so
2
The Box volumes, both in 2D and in 3D, only support boxes with edges parallel to the coordinate
axes. Non-axis aligned boxes can be defined using a convex polyhedron volume.
3
Tested 16 seconds on an Intel Core2 Q9300 (2.5GHz)
CHAPTER 4. GENGEO 81
Now the corner points can be used to define both the volume to be filled and the neigh-
bour table to contain the particles. The volume, which is of the class BoxWithPlanes3D
takes exactly two parameters in its constructor, describing the locations of two opposite
corners of the box. Therefore both parameters are of type Vector3. The class name
BoxWithPlanes3D, in particular the WithPlanes part will become clearer in the next
section 4.3. So to set up the box we use the previously defined corner points
# block volume
box = BoxWithPlanes3D(
minPoint=minPoint,
maxPoint=maxPoint
)
Next we need a neighbour table to store the particles. The constructor for the neighbour
table takes 4 arguments: two corner points (minPoint, maxPoint) to describe the volume
covered by the model, the grid spacing (gridSize) and the number of particle groups
(numGroups). The volume covered by the neighbor table should ideally be the bounding
box of the whole model, i.e. it needs to cover all particles but it should not be much
larger in order to minimize the memory used. The grid spacing determines the search
range used for the determining if two particles touch each other. To make sure all touching
(or intersecting) pairs of particles can be found the grid spacing needs to be larger than
twice the maximum particle radius. The number of particle groups will only be different
from 1 for rather complicated geometry set-up scripts, so we set numGroups=1 here. So
the construction of the neighbor table looks as follows:
# neighbour table
mntable = MNTable3D(
minPoint=minPoint,
maxPoint=maxPoint,
gridSize=2.5*maxRadius,
numGroups=1
)
Next we need to set up the packer which places the particles inside the volume. As
we want to pack single particles in 3D we chose a packer of type InsertGenerator3D.
The most simple constructor for this type of packer takes 5 arguments. In addition to
the minimum and maximum particle radius (minRadius, maxRadius) there are 3 para-
meters which control the performance of the packing algorithm. These are the number of
consecutive failed particle insertion attempts allowed before the algorithm gives up and
terminates(insertFails), the maximum number of iterations for the internal sphere fit-
ting procedure (maxIterations) and the precision of the sphere fitting, i.e. the maximum
tolerance up to which two particles are still considered touching (tolerance). The val-
ues we use here, insertFails=1000, maxIterations=1000 and tolerance=1e-6 usually
work fine for small to medium models. For large models it can be necessary to increase
the value of insertFails to obtain a good packing. The value of tolerance should be
considered in relation to the particle radii. A sixth parameter seed can be used to force
a re-seeding of the random number generator before the packing algorithm starts. Set-
ting seed=True guarantees a different packing with each run of the script, what happens
otherwise is system-dependent. Taking this all into account the packer is constructed like
this:
# packer
CHAPTER 4. GENGEO 82
packer = InsertGenerator3D(
minRadius=minRadius,
maxRadius=maxRadius,
insertFails=insertFails,
maxIterations=maxIter,
tolerance=tol
)
Now that the volume, the neighbour table and the packer, are defined the actual work
can start. First the packer needs to fill the volume with particles. In the simplest case
this requires two arguments to the generatePacking function of the packer: the volume
to fill and the neighbour table in which to store the particles. Therefore the function call
to fill the box volume with particles is
The last thing to do to complete the generation of the particle arrangement is to create the
bonds between touching particle pairs. Because the neighbour table contains all inform-
ations necessary to determine which particles should be bonded, i.e. particle positions
and radii, the bonding procedure is a member function of the neighbour table. The most
simple form of the function call, which just bonds all neighbouring particles in on group,
needs 3 arguments: the ID of the particle group which should be bonded groupID, the
bonding tolerance tolerance and the tag given to the newly created bonds bondID. Be-
cause our neighbour table contains only one group of particles, we need to set groupID=0.
The tolerance for bonding should normally be larger than the packing tolerance used in
the packer, so we chose 1e-5 here. The bond tag can be set to any value, but given that
we only have one set of bonds in this model we may as well use 0. So the call to create
the bonds looks as follows:
And after this the model is fully built. The only thing left to do is to write the model
data into a file so it can be used. For this purpose the neighbour table has a write
function which can write the information stored in the neighbour table, i.e. particles and
bonds, to a file in different ways which are controlled by the outputStyle argument.
If outputStyle=1 a geo file is written which can be used in ESyS-Particle scripts, if
outputStyle=2 a VTK file 4 is written for the use with visualisation software such as
Paraview(tm). Of course the write function also needs the file name as first argument,
so to write both a geo and a VTK file the calls should be:
Figure 4.1: An image (generated with Paraview) of the box filled with particles generated
by the script simple box.py described in section 4.2
mntable.write(
fileName="box.geo",
outputStyle=1
)
mntable.write(
fileName="box.vtu",
outputStyle=2
)
which concludes the GenGeo script to generate a box filled with bonded particles. The
script can now be executed as usual, i.e. python simple box.py. During execution
there will be some debug output, in particular for every 100 particles inserted GenGeo
will output a line stating how many particles have been inserted so far 5 and how many
attempts it took on average to insert one of the last 100 particles. When the execution
of the script finishes it should have produces two files, box.geo and box.vtu. When
visualized with Paraview the result should look similar to that shown in Figure 4.1. One
issue which is noticeable with this model is that the surfaces, and particularly the edges,
are rough, rather than smooth. How to overcome this is described in the next section.
5
This number does not include the particles generated during the seeding stage of the algorithm
CHAPTER 4. GENGEO 84
Figure 4.2: Images (generated with Paraview) of a box with particles fitted to the box
faces generated by the script smooth box.py described in section 4.3. The model in A)
uses particle radii between 0.2 and 1.0, the model in B) uses particle radii between 0.05
and 1.0.
model volume. For the box this can be calculated easily as the product of the x-, y- and
z-dimension like
volume = xdim*ydim*zdim
porosity = (volume - mntable.getSumVolume(groupID=0))/volume
where the groupID=0 argument to the getSumVolume call determined to which group of
particles in the neighbour table it is applied. While a comprehensive discussion of the
influence of the packing parameters on the resulting particle arrangement is beyond the
scope of this manual, a reasonable rule to make sure a dense packing is obtained for a
given problem is to use such a value of insertFails that the change of model porosity
resulting from a doubling of insertFails is of a similar order as the porosity variations
between different realizations of the model.
4. When generating the bonds, bonds between particles with different tags get a dif-
ferent tag than bonds between particles with the same tag 9
Point 1. above means that the box filling script from section 4.3 can be used as a starting
point, with just some small changes. In particular the number of particle groups in the
neighbour table needs to be 2 instead of one, i.e. the construction of the neighbour table
becomes
mntable = MNTable3D(
minPoint=minPoint,
maxPoint=maxPoint,
gridSize=2.5*maxRadius,
numGroups=2
)
7
Strictly speaking, the bonds between particles belonging to different clusters.
8
Approaches defining the clusters before packing the particles are also possible but much more com-
plicated and not within the scope of this manual.
9
Careful not to mix up particle tags and bond tags here!
CHAPTER 4. GENGEO 87
The next step is to generate a number of seed points distributed in the volume. For
simplicity we just arrange them in a regular grid here. In a production model a more
complicated arrangement of the seed points is usually needed, for example adding some
random displacement to the regular grid. First we specify the parameters of the regular
grid, in this case for a 3x6x3 arrangement of the clusters, i.e. we specify the number of
clusters in each direction and calculate the dimensions of the clusters from the size of the
box and the number of clusters per dimension. It would also be possible to do this the
other way around, but doing it this way guarantees that the clusters fit into the box, i.e.
we do not get smaller clusters at the edges of the box. If that would be an issue will
depend on the specific application the model is used for.
ncluster_x=3
ncluster_y=6
ncluster_z=3
dx=xdim/float(ncluster_x)
dy=ydim/float(ncluster_y)
dz=zdim/float(ncluster_z)
With these parameters a set of nested loops can be used to calculate the positions of the
seeds
for i in range(ncluster_x):
for j in range(ncluster_y):
for k in range(ncluster_z):
x=(float(i)+0.5)*dx
y=(float(j)+0.5)*dy
z=(float(k)+0.5)*dz
seed_pos=Vector3(x,y,z)
To tag each particle according to the seed closest to it the neighbour table member
function tagParticlesToClosest can be used. However, this function operates on two
sets of particles in the neighbour table, assigning tag of the closest particle in on group
to the particles in the other group. This means that the seeds actually need to be
particles in the neighbour table. However, because the seed particles are just dummy
particles and must not interact with the real particles they can not be simply inserted
in the neighbour table. This is where the concept of multiple particle groups becomes
useful: if the seed particles are inserted in a different particle group than the real particles
they are still part of the same neighbour table so member functions can operate on them,
but they otherwise do not interfere with each other. So the way to insert the seeds into
the neighbour table is to construct a particle, i.e. a Sphere object for each seed, give it
an appropriate tag and insert it into group 1 10 . The constructor of a Sphere object takes
two parameters, the center of the sphere, which is of type Vector3, and the radius of the
sphere, which is a float. Here a radius of 0.0 is used for the seed particles.
These instructions are still part of the loop above, so they have to be indented accordingly
(see full code at Appendix A.2.3). The tags of the seed particles are generated from the
loop variables making sure there is no duplication.
This completes the preparation for the generation of the clusters. The only things
left to do are to tag the particles according to the seeds and to generate the bonds.
The tagging is done using the tagParticlesToClosest function mentioned above. This
function takes two parameters: first the number of the group in which the particles are
tagged, i.e. the real particles, and second the group from which the tags are taken, i.e.
the seed particles. The bonds are generated using the generateClusterBonds member
function of the neighbor table. Similar to the simple generateBonds function it takes a
GroupID and a tolerance argument, but instead of a single bond tag argument it takes
two of those, bondTag1 for bonds between particles with the same tag, i.e. the intra-
cluster bonds, and bondTag2 for bonds between particles with different tags. If the tag
for intra-cluster bonds is set to 1 and the tag for the bonds between clusters is 2, the code
is
# generate bonds
mntable.generateClusterBonds(
GroupID=0,
tolerance=1.0e-5,
bondTag1=1,
bondsTag2=2
)
Because the seed particles are not part of the actual model geometry they should be
removed before writing the geometry to a file by calling
mntable.removeParticlesInGroup(
GroupID=1
)
where the GroupID parameter is the number of the group for which all particles are
removed 11 . This completes the script to fill a box with particles and to bond them
together in clusters. The resulting model is shown in Figure 4.3.
11
This requires at least rev. 120 of GenGeo. For older versions this has to be done less elegantly
by calling mntable.tagParticlesInGroup(1,1) to set the tag of all particles in group 1 to 1 and
mntable.removeTaggedParticles(1,1,-1) to remove them.12
12
N.B. There is a bug in removeTaggedParticles in GenGeo versions prior to rev. 120 which prevents
the use of a tag, mask combination fitting all tags, i.e. 0,0, to achieve the same result.
CHAPTER 4. GENGEO 89
Figure 4.3: Images (generated with Paraview) of a box with clustered particles generated
by the script cluster box.py described in section 4.5. Panel A) shows the particles
colored by particle tag. To enhance the visual contrast between neighboring clusters
colors are set using particle tag modulo 10. Panel B) shows the bonds in this model.
Intra-cluster bonds (bond tag 0) are shown in blue and tags between clusters (bond tag
1) in red.
4.8.1 Volumes
BoxWithJointSet
ConvexPolyhedron
SphereVol
ClippedSphereVol
CylinderVol
CylinderWithJointSet
DogBone
EllipsoidVol EXPERIMENTAL
Constructive Solid Geometry (CSG) based volumes EXPERIMENTAL
Objects
Tagging and Bonding
Whats Next?
CHAPTER 4. GENGEO 91
92
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 93
A.1.1 bingle.py
#bingle.py: A simple two-particle collision simulation using
# ESyS-Particle
# Author: D. Weatherley
# Date: 15 May 2007
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2007.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import Vec3, BoundingBox
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 95
scaling = True
)
)
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "bingle_data",
beginTimeStep = 0,
endTimeStep = 10000,
timeStepIncr = 100
)
)
camera = scene.getCamera()
camera.setLookAt(Vec3(0,0,0))
camera.setPosn(Vec3(0,0,20))
camera.setZoom(0.1)
scene.render(
offScreen=True,
interactive=False,
fileName="snap_{0:04d}.png".format(index),
size=[800,600]
)
return
sim.setSpatialDomain(domain)
A.1.5 POVsnaps.py
#POVsnaps.py: Implements an ESyS-Particle runnable for storing
# snapshots of particle simulations rendered using POVray
# Author: D. Weatherley
# Date: 17 May 2007
# Organisation: ESSCC, University of Queensland
# (C) All Rights Reserved, 2007.
def configure(
self,
lookAt=Vec3(0,0,0),
camPosn=Vec3(0,0,20),
zoomFactor=0.1,
imageSize=[800,600]):
self.lookAt=lookAt
self.camPosn=camPosn
self.zoomFactor=zoomFactor
self.imageSize=imageSize
def run(self):
if ((self.sim.getTimeStep() % self.interval) == 0):
self.snapshot()
self.count += 1
def snapshot(self):
pkg = povray
Scene = pkg.Scene()
plist = self.sim.getParticleList()
for pp in plist:
povsphere = pkg.Sphere(pp.getPosn(), pp.getRadius())
povsphere.apply(pkg.Colors.Red)
Scene.add(povsphere)
camera = Scene.getCamera()
camera.setLookAt(self.lookAt)
camera.setPosn(self.camPosn)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 102
camera.setZoom(self.zoomFactor)
fname = "snap_{0:04d}.png".format(self.count)
Scene.render(
offScreen=True,
interactive=False,
fileName=fname,
size=self.imageSize
)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 103
A.1.7 gravity.py
#gravity.py: A simple bouncing ball simulation using ESyS-Particle
# Author: D. Weatherley
# Date: 15 May 2007
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2007.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import Vec3, BoundingBox
from POVsnaps import POVsnaps
name = "elasticWall",
wallName = "floor",
normalK = 10000.0
)
)
NRotBondPrms(
name = "sphereBonds",
normalK = 10000.0,
breakDistance = 50.0,
tag = 1,
scaling = True
)
)
sim.createWall (
name = "floor",
posn = Vec3(0.0000, 0.0000, 0.0000),
normal = Vec3(0.0000, 1.0000, 0.0000)
)
timeStepIncr = 1000
)
)
centre = pp.getPosn()
radius = pp.getRadius()
Y = centre[1]
if (Y < 1.1*radius):
pp.setTag(12321) # tag particles nearest to the floor
sim.createParticle(pp) # add the particle to the simulation object
)
)
centre = pp.getPosn()
radius = pp.getRadius()
Y = centre[1]
if (Y < 1.1*radius):
pp.setTag(12321) # tag particles nearest to the floor
sim.createParticle(pp) # add the particle to the simulation object
A.1.13 floorMesh.msh
Triangle
3D-Nodes 6
0 0 0 -5.0 0.0 0.0
1 1 0 -5.0 0.0 5.0
2 2 0 0.0 0.0 -5.0
3 3 0 0.0 0.0 0.0
4 4 0 5.0 0.0 -5.0
5 5 0 5.0 0.0 5.0
Tri3 4
0 0 0 3 1
1 0 1 3 5
2 0 5 3 4
3 0 3 2 4
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 122
centre = pp.getPosn()
Y = centre[1]
if (int(Y%4) < 2):
pp.setTag(1) # layer 1
else:
pp.setTag(2) # layer 2
sim.createParticle(pp) # add the particle to the simulation object
)
)
interactionName2 = "friction"
)
wall_loader2 = WallLoaderRunnable(
LsmMpi = sim,
wallName = "bottom_wall",
vPlate = Vec3 (0.0, 0.125, 0.0),
startTime = 0,
rampTime = 50000
)
sim.addPreTimeStepRunnable (wall_loader2)
fieldName="Position",
fileName="out_Position.dat",
fileFormat="RAW_SERIES",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=10
)
sim.createFieldSaver(posn_saver)
A.1.16 WallLoader.py
#WallLoader.py: A Runnable for moving walls in ESyS-Particle simulations
# Author: D. Weatherley
# Date: 28 December 2008
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2008.
#
#
#import the division module for compatibility between Python 2 and Python 3
from __future__ import division
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
else:
f = 1.0
A.1.17 shearcell.py
#shearcell.py: An annular shear cell simulation using ESyS-Particle
# Author: D. Weatherley
# Date: 24 April 2011
# Organisation: ESSCC, The University of Queensland, Brisbane, AUSTRALIA
# (C) All rights reserved, 2011.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
from WallLoader import WallLoaderRunnable
from ServoWallLoader import ServoWallLoaderRunnable
tolerance = 1.0e-5
)
packer.generate()
particleList = packer.getSimpleSphereCollection()
sim.createWall (
name = "top_wall",
posn = Vec3 (0,10,0),
normal = Vec3 (0,-1,0)
)
sim.createInteractionGroup (
NRotFrictionPrms (
name = "pp_friction",
normalK = 1000.0,
dynamicMu = 0.6,
shearK = 100.0,
scaling = True
)
)
#particles near the base (tag=2) are bonded to the bottom wall:
sim.createInteractionGroup (
NRotBondedWallPrms (
name = "bwall_bonds",
wallName = "bottom_wall",
normalK = 1000.0,
particleTag = 2
)
)
#particles near the base (tag=3) are bonded to the top wall:
sim.createInteractionGroup (
NRotBondedWallPrms (
name = "twall_bonds",
wallName = "top_wall",
normalK = 1000.0,
particleTag = 3
)
)
wall_loader1 = WallLoaderRunnable(
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 136
LsmMpi = sim,
wallName = "bottom_wall",
vPlate = Vec3 (0.125, 0.0, 0.0),
startTime = 30000,
rampTime = 10000
)
sim.addPreTimeStepRunnable (wall_loader1)
force_saver = WallVectorFieldSaverPrms(
wallName=["bottom_wall", "top_wall"],
fieldName="Force",
fileName="out_Force.dat",
fileFormat="RAW_SERIES",
beginTimeStep=0,
endTimeStep=100000,
timeStepIncr=1
)
sim.createFieldSaver(force_saver)
)
)
A.1.18 ServoWallLoader.py
#import the division module for compatibility between Python 2 and Python 3
from __future__ import division
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
self.Nt += 1
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 139
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 140
# -- parameters --
# block dimensions
xdim=10
ydim=20
zdim=10
# corner points
minPoint = Vector3(0.0,0.0,0.0)
maxPoint = Vector3(xdim,ydim,zdim)
# block volume
box = BoxWithPlanes3D(minPoint,maxPoint)
# neighbour table
mntable = MNTable3D(minPoint,maxPoint,2.5*maxRadius,1)
# -- setup packer --
# iteration parameters
insertFails = 1000
maxIter = 1000
tol = 1.0e-6
# packer
packer = InsertGenerator3D(minRadius,maxRadius,insertFails,maxIter,tol)
# - input parameters --
# block dimensions
xdim=10
ydim=20
zdim=10
# corner points
minPoint = Vector3(0.0,0.0,0.0)
maxPoint = Vector3(xdim,ydim,zdim)
# neighbour table
mntable = MNTable3D(minPoint,maxPoint,2.5*maxRadius,1)
# block volume
box = BoxWithPlanes3D(minPoint,maxPoint)
# boundary planes
bottomPlane=Plane(minPoint,Vector3(0.0,1.0,0.0))
leftPlane=Plane(minPoint,Vector3(1.0,0.0,0.0))
frontPlane=Plane(minPoint,Vector3(0.0,0.0,1.0))
topPlane=Plane(maxPoint,Vector3(0.0,-1.0,0.0))
rightPlane=Plane(maxPoint,Vector3(-1.0,0.0,0.0))
backPlane=Plane(maxPoint,Vector3(0.0,0.0,-1.0))
# -- setup packer --
# iteration parameters
insertFails = 1000
maxIter = 1000
tol = 1.0e-6
# packer
packer = InsertGenerator3D( minRadius,maxRadius,insertFails,maxIter,tol,False)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 143
# - input parameters --
# block dimensions
xdim=10
ydim=20
zdim=10
# corner points
minPoint = Vector3(0.0,0.0,0.0)
maxPoint = Vector3(xdim,ydim,zdim)
# neighbour table
mntable = MNTable3D(minPoint,maxPoint,2.5*maxRadius,2)
# block volume
box = BoxWithPlanes3D(minPoint,maxPoint)
# boundary planes
bottomPlane=Plane(minPoint,Vector3(0.0,1.0,0.0))
leftPlane=Plane(minPoint,Vector3(1.0,0.0,0.0))
frontPlane=Plane(minPoint,Vector3(0.0,0.0,1.0))
topPlane=Plane(maxPoint,Vector3(0.0,-1.0,0.0))
rightPlane=Plane(maxPoint,Vector3(-1.0,0.0,0.0))
backPlane=Plane(maxPoint,Vector3(0.0,0.0,-1.0))
# -- setup packer --
# iteration parameters
insertFails = 1000
maxIter = 1000
tol = 1.0e-6
# packer
packer = InsertGenerator3D( minRadius,maxRadius,insertFails,maxIter,tol,False)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 145
# regular grid
for i in range(ncluster_x):
for j in range(ncluster_y):
for k in range(ncluster_z):
x=(float(i)+0.5)*dx
y=(float(j)+0.5)*dy
z=(float(k)+0.5)*dz
seed_pos=Vector3(x,y,z)
tag=i*ncluster_y*ncluster_z+j*ncluster_z+k
# construct & insert seed "pseudo-particle"
cseed=Sphere(seed_pos,0.0)
cseed.setTag(tag)
mntable.insert(cseed,1)
# generate bonds
mntable.generateClusterBonds(0,1.0e-5,1,2)
146
APPENDIX B. INTERACTION GROUPS & FIELDS 147
File Formats
149
APPENDIX C. FILE FORMATS 150
C.2.1 RAW
This format produces one file per saved time step, containing a particle position and the
field value for each field value. In case of a ParticleFieldSaver the position is that of the
particle, in case of a InteractionFieldSaver its the location of the first particle (i.e. the one
with the lower ID) 1 involved in the interaction. The files therefore consist of 4 columns
in case of a scalar field px py pz v or 6 colums in case of a vector field px py pz vx vy
vz where
C.2.2 RAW2
This format produces one file per saved time step,
id1 id2 in columns 1, 2 are the particle IDs of the two particles involved in the
interaction
p1x p1y p1z in columns 3, 4, 5 are the x-, y- and z-component of position of the
first particle
p2x p2y p2z in columns 6, 7, 8 are the x-, y- and z-component of positionof the
second particle
ipx ipy ipz in columns 9, 10, 11 are the x-, y- and z-component of the interaction
location, i.e. for most interaction types roughly the contact point between the
particles
v in column 12, or in case of a vector field, vx vy vz in columns 12, 13, 14, represent
the value of the saved field for the interaction
C.2.6 SUM
C.2.7 MAX