You are on page 1of 153

ESyS-Particle Tutorial and Users Guide

Version 2.3
D. Weatherley, W. Hancock & V. Boros
The University of Queensland
S. Abe
Institute for Geothermal Resource Management
29 January 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 Specication of the spatial domain . . . . . . . . . . . . . . . . . . 11
2.1.3 Particle creation and initialisation . . . . . . . . . . . . . . . . . . . 11
2.1.4 Denition 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 innite 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 ow: 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 ow: 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
A Code-listings for tutorial examples 92
A.1 ESyS-Particle scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
A.1.1 bingle.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
A.1.2 bingle output.py . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
A.1.3 bingle chk.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
A.1.4 bingle vis.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
A.1.5 POVsnaps.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
A.1.6 bingle Runnable.py . . . . . . . . . . . . . . . . . . . . . . . . . . 103
A.1.7 gravity.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
A.1.8 gravity cube.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
A.1.9 slope fail.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
A.1.10 slope friction.py . . . . . . . . . . . . . . . . . . . . . . . . . . 112
A.1.11 slope friction floor.py . . . . . . . . . . . . . . . . . . . . . . . 115
A.1.12 slope friction walls.py . . . . . . . . . . . . . . . . . . . . . . . 118
A.1.13 floorMesh.msh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
A.1.14 hopper flow.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
A.1.15 rot compress.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
A.1.16 WallLoader.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
A.1.17 shearcell.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
A.1.18 ServoWallLoader.py . . . . . . . . . . . . . . . . . . . . . . . . . . 138
A.2 GenGeo examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
A.2.1 simple box.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
A.2.2 smooth box.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
A.2.3 cluster box.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
B Interaction Groups & Fields 146
B.1 Interaction Groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
B.2 Field Saver Field Names for Common Particle Types and Interaction Groups148
C File Formats 149
C.1 Descriptions and Output File Formats for Field Saver Field Names . . . . 150
C.2 Descriptions of the Output File Formats for Field Savers . . . . . . . . . . 151
C.2.1 RAW . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
C.2.2 RAW2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
C.2.3 RAW SERIES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
CONTENTS 4
C.2.4 RAW WITH ID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
C.2.5 RAW WITH POS ID . . . . . . . . . . . . . . . . . . . . . . . . . . 151
C.2.6 SUM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
C.2.7 MAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
List of Figures
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
3.1 A sequence of snapshots from a frictionless slope collapse simulation (from
slope fail.py) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.2 A sequence of snapshots of slope collapse with frictional particles (from
slope friction.py) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.3 A sequence of snapshots of slope collapse with frictional particles and a
frictional oor (from slope friction floor.py) . . . . . . . . . . . . . . 41
3.4 A sequence of snapshots of slope collapse utilising quarter symmetry (from
slope friction walls.py) . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.5 Specication of mesh nodes for wall normals . . . . . . . . . . . . . . . . . 44
3.6 Hopper ow using a mesh base in quarter symmetry (from hopper flow.py) 46
3.7 Diagram illustrating the forces and moments between particles bonded via
rotational elastic-brittle bonds . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.8 Diagram illustrating a typical stress-strain curve & how to measure Youngs
modulus (E) & the unconned compressive strength (UCS) from such a curve 56
3.9 Stress-Strain curve obtained from a uniaxial compression simulation (from
rot compress.py) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.10 Time-series of total strain energy stored in bonds during a uniaxial com-
pression simulation (from rot compress.py) . . . . . . . . . . . . . . . . . 61
3.11 Time-series of percentage of bonds broken during a uniaxial compression
simulation (from rot compress.py) . . . . . . . . . . . . . . . . . . . . . . 62
3.12 Time-series of net wall force during a uniaxial compression simulation (from
rot compress.py) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.13 An example of an ESyS-Particle simulation visualised using ParaView. . . 65
3.14 Examples of visualisation using Glyphs in ParaView. A) Particles rep-
resented as sphere glyphs, coloured by the X-component of velocity; B)
Particle velocities represented as arrow glyphs, coloured by the speed of
each particle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
3.15 Diagram of a two-dimensional annular shear cell simulation employing pe-
riodic boundaries in the X-direction (from shearcell.py). . . . . . . . . . 69
3.16 Time-series of the eective bulk friction coecient from a two-dimensional
annular shear cell simulation (from shearcell.py). . . . . . . . . . . . . . 76
3.17 Time-series of the top wall position from a two-dimensional annular shear
cell simulation (from shearcell.py). . . . . . . . . . . . . . . . . . . . . . 77
5
LIST OF FIGURES 6
4.1 An image (generated with Paraview) of the box lled 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 tted 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
Introduction to the ESyS-Particle
DEM simulation software
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 simplied 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
nite dierence 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 rst-order nite dierence time integration scheme is employed. Provision is made
for le storage of both the entire model state or specic eld 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,
dene the types of interactions acting on these objects,
select the types and frequency of data output during simulations, and
perform user-dened 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 appli-
cations for the DEM, namely:
ideal gas dynamics involving collisions between indivisible particles,
gravitational acceleration of individual particles or bonded groups of particles,
sandpiles and landslides,
hopper (or silo) ow,
brittle failure of solids under uniaxial compression, and
shear of granular media within an annular shear cell apparatus.
DISCLAIMER: The simulation scripts described in this Guide and provided in
the appendices are for instructional purposes only. These scripts are not consid-
ered production-ready for applied numerical modelling, and parameter values used
may not lie in physical ranges for the application areas discussed. No warranty or
guarantee is given by the authors as to the utility of these scripts for research and
development purposes. It is the responsibility of the user to ensure ESyS-Particle
simulations are veriable and validated for any particular application.
Chapter 2
Simple Models
2.1 A simple simulation: collision of two particles
This section introduces the basic features of ESyS-Particle simulation scripts via a simple
example: collision of two indivisible particles. The simulation consists of two particles of
diering mass whose initial velocities are selected to ensure that the particles will collide.
This example serves to illustrate the main components of any ESyS-Particle simulation
script, namely:
Initialisation of an ESyS-Particle simulation object
Specication of the spatial domain
Particle creation and initialisation
Denition of inter-particle interactions
Execution of time integration
The complete code-listing for this example is provided in Appendix A.1.1, entitled
bingle.py
1
. 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 identiable by the teletext font. Shell commands will be prepended with the $
symbol.
2.1.1 Initialisation of an ESyS-Particle simulation
In order to use the ESyS-Particle simulation libraries in Python, we must rst import the
modules we wish to use. For this rst example, only the following two import statements
are required:
from esys.lsm import *
from esys.lsm.util import Vec3, BoundingBox
The rst 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 spec-
ifying 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 dene 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, dierent types of interactions and data output components. The
following code-fragment creates a simulation object:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
The rst statement creates an LsmMpi object and takes two arguments. The numWorker
Processes argument species 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) species the manner in which the domain will be divided amongst the
worker processes. The rst 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 specied by the mpiDimList.
The second statement (sim.initNeighbourSearch) species 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 denes 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
2.1.2 Specication of the spatial domain
Prior to addition of particles, the simulation object must be assigned a valid spatial
domain. Any particles or walls residing outside this domain are eliminated from force cal-
culations and time integration. The following code-fragment species the spatial domain
for a simulation:
sim.setSpatialDomain (
BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
)
The spatial domain is dened by a rectangular BoundingBox. In this example the spatial
domain is a cube with side length of 40.0 metres, with the left, bottom, back corner at
the position (x, y, z) = (20, 20, 20).
2.1.3 Particle creation and initialisation
There are a number of ways to create and add assemblies of particles to a simulation
object. We will encounter a few of the most common methods in subsequent tutorials.
For this example, we will insert two particles individually. The following code-fragment
creates a particle and initialises its linear velocity:
particle=NRotSphere(id=0, posn=Vec3(-5,5,-5), radius=1.0, mass=1.0)
particle.setLinearVelocity(Vec3(1.0,-1.0,1.0))
sim.createParticle(particle)
The rst statement creates a NRotSphere object called particle whose id will be
set to 0 with a radius of 1.0 metre, a mass of 1.0 kilogram, and initially centred on the
point (x, y, z) = (5, 5, 5). The second statement initialises the linear velocity of the
particle to (V
x
, V
y
, V
z
) = (1.0, 1.0, 1.0) metres/second. Finally the third statement adds
the newly created particle to our simulation object.
EXERCISE: Add a second particle to the simulation object with id of 1, a
radius of 1.5 m and a mass of 2.0 kg. Set the initial position of the particle
to (5, 5, 5) and its linear velocity as (1, 1, 1) (HINT: simply copy the code-
fragment above and modify as necessary).
2.1.4 Denition of inter-particle interactions
Having added two particles to our simulation object, we must now specify the type of
interactions between the particles if they should come into contact (which they will due
to the carefully selected initial positions and velocities above). There are a number of
dierent types of particle-particle interactions that may be used. We will encounter most
of the more common interaction types in this and subsequent tutorials. For this example
we will choose the simplest type of interaction linear elastic repulsion between non-
rotational spheres. The following code-fragment achieves this:
sim.createInteractionGroup(
NRotElasticPrms(
name = "elastic_repulsion",
normalK = 10000.0,
scaling = True
)
)
CHAPTER 2. SIMPLE MODELS 12
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 species purely elas-
tic interactions between unbonded non-rotational spheres, with an elastic stiness of
10000 N/m. When scaling is True (the default), the normal stiness scales with parti-
cle size. The name argument assigns the InteractionGroup a unique user-dened 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, dierent types of interactions may be specied including body
forces (e.g. gravity), particle-wall interactions and various types of particle-particle inter-
actions. Some of these dierent interaction types will also be encountered in subsequent
tutorials.
2.1.5 Execution of time integration
We have now done everything necessary to set up the simulation. All that remains is to
instruct Python to go ahead and do all the computations:
sim.run()
Although this statement might appear to be something of an anti-climax, it is the most
important, instructing ESyS-Particle to commence the simulation and compute forces
and update particle positions repeatedly until the specied number of timesteps are com-
pleted. Once this command is executed, the C++ simulation engine takes control and no
subsequent Python commands will be executed until the simulation is completed. In the
next tutorial, we will encounter three dierent ways to instruct ESyS-Particle to execute
additional python commands during the simulation.
2.1.6 Running an ESyS-Particle simulation from the comman-
dline
Using your favorite text editor copy all the code-fragments above into a text le and save
it as bingle.py. Make sure you insert all the code-fragments in the order they appear
here (and dont forget to add the second particle). Having saved the script, we can run
the simulation using the following shell command:
$ mpirun -np 2 which esysparticle bingle.py
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 species the number of
slaves to use but MPI must initialise one extra process to act as the master.
CHAPTER 2. SIMPLE MODELS 13
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
messages
2
) the simulation has correctly computed the trajectories of both particles for an
interval of 10 sec. During that time the particles collided then bounced o 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 le or the
screen. The following tutorial will describe three dierent 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 nd 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
2.2 Data output during simulations
This tutorial extends the bingle.py simulation constructed in the previous tutorial. The
primary aim here is to introduce three ways to output data during simulations. We will
rst examine the simplest way to output data printing particle positions to the screen.
The second method involves output of data to a text le using a built-in feature called
a CheckPointer. Although these techniques are often very useful for testing purposes or
postprocessing of simulation data, they are certainly not as visually satisfying as producing
glossy images or movies of the particles in motion. Finally we will examine how one of
the ESyS-Particle visualisation modules can be used to generate images of the particle
assembly.
A secondary aim of this tutorial is to demonstrate three dierent approaches for in-
cluding your own code in ESyS-Particle scripts. These approaches are:
direct insertion in the time-integration loop,
writing and calling a user-dened subroutine, and
use of a user-dened Runnable module.
The use of Runnable modules is arguably the best method, even though it appears com-
plicated at rst. Runnables have the advantage that they can be very easily reused for
other simulations with little or no modication. 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 congure your shell environment
so that Python can nd your modules easily.
2.2.1 Printing simulation data to screen
This example modies our original bingle.py script so that the positions of both particles
can be output to screen every 100 timesteps. This represents one of the most basic of
data output operations and allows us to do our rst real analysis of the simulation results
(e.g. by utilising external software to plot particle positions vs. time). The complete
code-listing for this example is provided in Appendix A.1.2, called bingle output.py.
Replace the last line (sim.run()) of bingle.py with the following code-fragment:
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 rstly 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.
WARNING: Care should be taken when using sim.getParticleList() in a
simulation containing a very large number of particles. For large simulations, the
amount of memory required to store the list by the master process may be very
large. It is usually best to avoid using sim.getParticleList() if possible. In
the next tutorial we will encounter another method (the CheckPointer) to store
information about particles that avoids using a large amount of memory.
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 modied script to a text le 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
1000 -3.999 3.999 -3.999 3.999 3.999 3.999
1100 -3.899 3.899 -3.899 3.899 3.899 3.899
1200 -3.799 3.799 -3.799 3.799 3.799 3.799
1300 -3.699 3.699 -3.699 3.699 3.699 3.699
1400 -3.599 3.599 -3.599 3.599 3.599 3.599
1500 -3.499 3.499 -3.499 3.499 3.499 3.499
1600 -3.399 3.399 -3.399 3.399 3.399 3.399
1700 -3.299 3.299 -3.299 3.299 3.299 3.299
1800 -3.199 3.199 -3.199 3.199 3.199 3.199
1900 -3.099 3.099 -3.099 3.099 3.099 3.099
Re-run the simulation with the following shell command (typed on one line) to redirect
the screen output to a text le called simdata.csv:
$ mpirun -np 2 which esysparticle bingle_output.py > simdata.csv
One can then use this text le to plot the trajectories of each particle, utilising your
favourite graphing software. For example, import simdata.csv into a spreadsheet pro-
gram and produce an XY plot of column two versus column one and column ve versus
column one. Your plot should be similar to Figure 2.1. Note that the two trajectories do
not intersect although the particles are deected in a manner consistent with collision.
This is because the two particles are of nite radius and they collide only at their surfaces
(whereas we are only plotting the trajectories of the centre-of-mass of the particles).
2.2.2 Data output using the ESyS-Particle CheckPointer
The simple example in the previous section is quite useful for debugging purposes, when
one wishes only to monitor the movement of a few particles. In simulations involving
many thousands of particles, one would prefer to output the positions, velocities and
accelerations of all the particles at regular intervals so the data may be post-processed in
various ways. ESyS-Particle includes a module known as a CheckPointer for this purpose.
The CheckPointer is a special case of a group of modules known as FieldSavers, which we
will discuss in a future tutorial. FieldSavers provide a mechanism to selectively output
information on particles (such as position or kinetic energy), interactions (e.g. potential
energy) and walls (including their position and the net force acting on a wall).
A CheckPointer is designed to write text les at regular intervals containing the po-
sitions, velocities and accelerations of all particles. To implement a CheckPointer in a
simulation is a relatively simple procedure. Return to your bingle.py script created
in the rst tutorial. Just before the sim.run() subroutine call, add the following code
fragment:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "bingle_data",
beginTimeStep = 0,
endTimeStep = 10000,
timeStepIncr = 100
)
)
The CheckPointer takes four parameters:
CHAPTER 2. SIMPLE MODELS 17
fileNamePrefix: specifying the lename prex for all the output les to be written
during the simulation,
beginTimeStep: the timestep number to commence saving data,
endTimeStep: the timestep number to conclude saving data, and
timeStepIncr: the number of timesteps to complete between each save.
The example above will save simulation data every 100 timesteps, commencing at the rst
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:
$ mpirun -np 2 which esysparticle bingle_chk.py
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 les 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 le was saved. The rst (ending with 0.txt) is a header le containing information
about the format of the corresponding data le (ending with 1.txt). Depending upon
the type of particles used, whether or not you have mesh walls etc., the format of the
checkpoint les changes so that all relevant information about the simulation is saved at
the specied times.
Lets examine one of the output les 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 rst line of the le species 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 species the number of triangle mesh walls
and the last line species 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 ow simulation.
Returning to the lines providing data on each particle, for simulations using NRot
Sphere particles (as we are using here), the elds in each data line correspond to the
following:
elds 1, 2 & 3: the X, Y and Z-coordinates of the current particle position
eld 4: the particle radius
eld 5: the particle ID
eld 6: the particle tag (more on tags later)
eld 7: the particle mass (recall that we set the mass of the second particle to be
2.0)
elds 8, 9 & 10: the X, Y and Z-coordinates of the initial particle position
elds 11, 12 & 13: the X, Y and Z-coordinates of the previous particle position
elds 14, 15 & 16: the X, Y and Z-components of the particle velocity
elds 17, 18 & 19: the X, Y and Z-components of the net force acting on the particle
elds 20, 21 & 22: (used with circular or periodic boundaries) species the circular
shift to be added in the X, Y and Z-directions
Your rst 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 dierent 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.
2.2.3 Generation of particle snapshots (via subroutine calls)
The previous two examples illustrated simple ways to output data from ESyS-Particle
simulations. In this example we will introduce a way to generate beautiful snapshots
of the particle assembly at various times during a simulation. This is achieved using
the ESyS-Particle esys.lsm.vis.povray module and an external package called POVray
that is ideal for generating images of 3D objects or scenes
3
. The ESyS-Particle povray
module provides a relatively simple interface between particle simulations and POVray.
3
Take a break for the moment and enjoy the amazing computer-generated images in the POVray Hall
of Fame!
CHAPTER 2. SIMPLE MODELS 19
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 rst 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:
from esys.lsm.vis import povray
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 eects, but it lacks tools specically designed
for scientic visualisation of datasets (e.g. for generating isosurfaces or contours). vtk
is the opposite, providing great scientic 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 le.
The code-fragment for implementing this subroutine is as follows:
def snapshot(particles=None, index=0):
pkg = povray
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 rst line of this code-fragment denes the name of the subroutine (snapshot) and
species that it accept two keyword arguments:
particles a pointer to a list of particles (e.g. obtained from the sim.getPartic
leList() subroutine), and
CHAPTER 2. SIMPLE MODELS 20
index a unique identier used to specify the name of the le in which to store the
rendered image.
Within the subroutine, we rstly 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 dening 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 dierent 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 rst 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 nally 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 nal image:
offScreen a Boolean variable that determines whether the image will appear in
a window onscreen or be rendered oscreen (in the background).
interactive a Boolean variable specifying whether the user is permitted to inter-
act with the rendered image. If this argument is set to True and offScreen=False,
a window will appear and the user will be able to pan and zoom the image using
the mouse or keyboard. Only the vtk package provides interactive features.
filename a text string specifying the name of the le in which to save the image.
The lename 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 nal 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 lename argument above uses Python string formatting syntax to con-
struct a lename 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 produc-
ing 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 denition
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 le rendered when the subroutine is called.
Save the resulting script to a text le 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 les 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
Figure 2.2: A sequence of snapshots (from bingle vis.py)
CHAPTER 2. SIMPLE MODELS 23
2.2.4 A Runnable module for generating snapshots
Thus far in our data output examples we have had to make use of an explicit time-
integration loop (instead of the simple and elegant sim.run() call), used a CheckPointer
to store particle data and, in the previous example, have added an additional subroutine
to our simulation script. This has resulted in a rather lengthy script. To compound the
issue, we would need to do this for every simulation script we write, resulting in many large
scripts containing much duplicate code. From a design and implementation perspective
this is unwieldy and multiplies the possibility for errors in the code.
ESyS-Particle provides a mechanism to resolve many of these issues in the form of a
Runnable class. A Runnable is a user-dened class that can be called by a simulation ob-
ject once per timestep either before or after the force computations and time-integration.
Runnable classes can be implemented as modules that can be reused in subsequent sim-
ulations simply by adding an import statement and some lines of initialisation code to
your simulation script. This is a powerful feature of ESyS-Particle, oering the user the
possibility to develop runtime data analysis and visualisation utilities that can be reused
whenever they are needed with little or no modication to the Runnable itself.
The aim for this tutorial example is to implement the snapshot() subroutine from
the previous example as a Runnable module. We will discuss how to call this from within
your simulation script and how to deploy the module so that it can be easily reused in
subsequent simulation scripts. If you are not already familiar with classes and inheritance
in Python, I recommend you rst study Chapter 9 of the Python Language Tutorial.
We will also touch on some topics covered in Chapter 6 of the Python Tutorial. The
complete code-listings for this example are found in Appendix A.1.5 POVsnaps.py and
Appendix A.1.6 bingle Runnable.py.
Implementation of a snapshot Runnable
A Runnable is best implemented in its own text le rather than inserting it in a simulation
script (although you may do this if you wish). We will implement our Runnable in a text
le called POVsnaps.py. Like any other ESyS-Particle script, we commence with relevent
import statements:
from esys.lsm import *
from esys.lsm.util import Vec3, BoundingBox
from esys.lsm.vis import povray
A user-dened 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 denition and initialisation subroutine:
class POVsnaps (Runnable):
def __init__(self, sim, interval):
Runnable.__init__(self)
self.sim = sim
self.interval = interval
self.count = 0
self.configure()
CHAPTER 2. SIMPLE MODELS 24
The rst line denes the name of the class POVsnaps and species that it inherits
from the Runnable base class. Next we encounter the denition 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 rst 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 dene the image lenames.)
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
Our self.configure() subroutine accepts four optional keyword arguments: lookAt,
camPosn, zoomFactor and imageSize. Each of these arguments is provided with a default
value in the subroutine denition (the rst line). When this subroutine is called, any
keyword argument that is not assigned a value by the calling statement will be set to the
default value specied here. The call to self.configure() in the initialisation subroutine
species no keyword arguments, so all of these arguments will be set to their default values.
The implementation of this subroutine is straight-forward: we simply create an internal
data member to store the value for each argument. These data members will be used in
the self.snapshot() subroutine later.
ASIDE: You may be wondering why we have implemented an extra subroutine
for conguring these data members. We could have included these as keyword
arguments of the initialisation subroutine. The rationale for implementing this
extra subroutine is that we can use this subroutine to recongure our POVsnaps
Runnable during a simulation. This feature might be used, for example, to change
the camera position during a simulation to produce a y-through animation of the
particle assembly. The initialisation subroutine can only be called once but the
configure(..) subroutine can be called many times.
ESyS-Particle Runnable subclasses must also implement a self.run() subroutine.
This subroutine is called by simulation objects once per timestep and it is here that
we implement the code that should be executed by the Runnable each timestep. The
following code-fragment implements the mandatory self.run() subroutine:
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)
fname = "snap_%.4d.png" % (self.count)
Scene.render(
offScreen=True,
interactive=False,
fileName=fname,
size=self.imageSize
)
You will notice only a few minor dierences though. We now use the data members
dened in the self.configure() subroutine to initialise the camera and the image size.
By using these data members we can easily congure our snapshot Runnable for dierent
simulations or perhaps recongure the camera during a simulation if we would like, for
example, to create a y-through animation.
Use of the snapshot Runnable in a simulation
Having copied all the code-fragments into a text le called POVsnaps.py, we are ready to
use our Runnable in a simulation. We will modify bingle.py, the script we wrote that
didnt produce any data output. Firstly, add an import statement to the top of the script:
from POVsnaps import POVsnaps
This statement will instruct Python to search for a le called POVsnaps.py and import
the POVsnaps class implemented therein. Having done this, Python now knows where to
nd 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:
povcam = POVsnaps(sim=sim, interval=100)
sim.addPostTimeStepRunnable(povcam)
CHAPTER 2. SIMPLE MODELS 26
The rst statement creates a POVsnaps object called povcam. We have specied 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 le 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 denition). We have not only avoided tiring out our
ngers 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.
Deploying a Runnable as a reusable module
Whenever Python encounters an import statement, it will search a number of default
directories for the appropriate python scripts implementing the imported modules. These
default directories are typically places like /usr/lib/Python2.4/site-packages or
/usr/local/lib/Python2.4/site-packages. If Python cannot nd the script, it will
search the current working directory. If Python still cannot nd the script, it will crash
with an error message.
As a user, you have two options for reusing your Runnable module. Firstly you could
copy your Runnable module script (e.g. POVsnaps.py) into the current working directory
whenever you wish you use it. This can get tedious, so a better way is to copy the script
to a directory where you intend to store all your useful Runnable module scripts. To do
this rst create a directory to store the scripts and copy your script into that directory:
$ 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 dierent 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 le
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 nd 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 le at specied 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 les 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 rst 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 diering 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
2.3 Bouncing balls: adding gravity, walls and bonded
particles
This tutorial aims to build upon the techniques introduced previously, to demonstrate
more of the basic components of ESyS-Particle simulations. In particular, we will ex-
amine how body forces (such as gravity and viscosity), planar walls and bonded clusters
of particles are implemented. By way of motivation, we will consider the problem of
simulating an elastic body dropped from a height upon a frictionless table under the in-
uence of gravity and air resistance. Intuitively we expect the body to bounce o the
table-top, rise up to a height lower than the original height, then fall back towards the
table. After numerous bounces, the body should come to rest on the table-top. To make
the simulation more interesting we will start with a single particle representing our elastic
body then replace this with a cubic cluster of bonded particles. This relatively simple
problem serves to illustrate some new techniques that have wide applicability in particle
simulations. As always complete code-listings are provided in Appendix A.
EXERCISE: Write a script that initialises a simulation object and adds a
single particle that is initially at rest, located at (x, y, z) = (0, 5, 0). Assign an
initial velocity of (V
x
, V
y
, V
z
) = (1.0, 10.0, 1.0). You may use the same spatial
domain as the bingle.py tutorial, i.e., a cubic region with opposite corners
at (20, 20, 20) and (20, 20, 20). At this stage you do not need to add any
InteractionGroups. Save the script in a text le called gravity.py.
2.3.1 Implementation of body forces: gravity and bulk viscosity
There are three basic types of interactions in ESyS-Particle: inter-particle interactions,
body forces, and particle-wall interactions. We have already encountered inter-particle
interactions in our two-particle collision tutorial. This section will introduce two of the
most common body forces: gravity and bulk viscosity. The following section introduces
walls and particle-wall interactions.
Gravitational Interactions
Gravity is implemented in ESyS-Particle via an InteractionGroup specied by a param-
eter 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 rst argument (name) is a user-dened
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 specied 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 signicantly 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 denes 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 coecient 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 coecient of viscosity depends upon the particular problem
to be solved. For elastodynamic problems, a small value is sucient (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 sucient damping of the balls motion over an interval of 20 sec. to simulate
damped oscillations of a bouncing ball.
2.3.2 Implementation of innite planar walls and particle-wall
interactions
Frequently one would like to incorporate xed or movable walls in particle simulations.
Walls may be planar, piece-wise planar, or perhaps an arbitrary shape. ESyS-Particle
implements three types of walls:
Planar walls innite planar walls specied by a point and a normal vector.
Linear meshes a piece-wise linear mesh of line segments for arbitrarily shaped
walls in 2D simulations.
Triangular meshes a mesh of triangles used to dene surfaces in 3D simulations.
IMPORTANT: All three types of wall have an active side and an inactive
side. For the case of an innite wall, the normal vector points to the active side
of the wall. Particles impinging on a wall from the active side will bounce o 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 specied 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 ow simuation.
CHAPTER 2. SIMPLE MODELS 30
For our bouncing ball simulation, we will implement an innite 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 species 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 insucient. We must also dene
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
)
)
Particle-wall interactions are also implemented via an InteractionGroup. This time
we pass a NRotElasticWallPrms parameter set used to specify elastic repulsion of non-
rotational spheres from a wall. The wallName argument species to which wall this
particle-wall interaction refers. We make use of the wall name here to identify the wall
under consideration. The last argument (normalK) species the elastic stiness of the
particle-wall interaction in units of N/m.
The choice of elastic stiness is not arbitrary. We need to assign an elastic stiness
suciently large that the wall can support the weight of the particle with a relatively small
indentation (or overlap). If the elastic stiness is too small the particle will continue to
fall through the wall and eventually fall out the other side!
4
Having created this particle-wall interaction group, the simulation will now track the
locations of particles relative to the wall and apply an elastic restoring force to any
particle that comes into contact with the wall. Add all the code-fragments above (for
gravity, viscosity and the wall) to your gravity.py script and run a simulation con-
sisting of 20000 timesteps with a timestep increment of 0.001 sec. Use your POVsnaps
Runnable to make snapshots of the particle during the simulation. You may need to call
the povcam.configure() subroutine when you initialise your POVsnaps object to change
the camera position and/or look-at point. If all goes well, you should see the particle
bounce o the wall a few times, with a reducing bounce height after successive bounces.
Figure 2.3 illustrates the movement of the ball graphically.
4
Try it yourself: For a ball of mass m and radius r in a gravitational eld with acceleration g, the
elastic stiness must be K
wall
> mg/r to prevent the particle falling through the wall.
CHAPTER 2. SIMPLE MODELS 31
Figure 2.3: Trajectory of a ball bouncing under gravity with linear viscosity (from
gravity.py)
2.3.3 Generating a bonded lattice of particles
In the next example we will modify our bouncing ball script to replace the single particle
with a bonded cube of particles. This serves to illustrate the three steps for generating
an assembly of particles:
1. generating a block of unbonded particles,
2. creating bonds between neighbouring particles, and
3. specifying the type of interactions between bonded particle-pairs.
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.
Generating a block of unbonded particles
ESyS-Particle provides four methods for generating a block of particles:
SimpleBlock generates a block of particles whose centres-of-mass reside on the
vertices of a regular cubic lattice. This is the simplest conguration but is typically
not the best choice for serious simulations since the particle-packing is not ideal (the
porosity is very high).
CubicBlock generates a Face-Centred Cubic (FCC) lattice of particles with a
dense packing arrangement.
HexagBlock generates a Hexagonal Close Packing (HCP) of particles.
CHAPTER 2. SIMPLE MODELS 32
RandomBoxPacker generates a block of particles with radii randomly distributed
in a specied range.
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 dierent 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:
from esys.lsm.geometry import CubicBlock, ConnectionFinder
The following code-fragment generates an FCC cubic block of particles and adds them
to the simulation object:
cube = CubicBlock(dimCount=[6,6,6], radius=0.5)
cube.rotate(axis=Vec3(0,0,3.141592654/6.0),axisPt=Vec3(0,0,0))
sim.createParticles(cube)
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 6m6m6m).
The second command (cube.rotate(..)) rotates the cube by 30 degrees about the
Z-axis. This is done so the cube will rst strike the oor on an angle rather than at
on one face. This should make the subsequent bounces a bit more interesting! The nal
command adds our cube of particles to the simulation object.
Creation of inter-particle bonds
By default the CubicBlock class creates an unbonded particle assembly. In order to
construct a bonded particle assembly we must explicitly inform the simulation object
that particles are to be bonded together. The following code-fragment achieves this:
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
specied 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
Specication of bonded-particle interactions
Once we have inserted the block of particles into the simulation object and bonded the
particles together, we must specify the type of interactions between bonded particles and
initialise the interaction parameters. Once again, we utilise an InteractionGroup with
an appropriate parameter set. For this example we will create non-rotational elastic bonds
between bonded particles as specied in the NRotBondPrms parameter set:
bondGrp = sim.createInteractionGroup(
NRotBondPrms(
name = "sphereBonds",
normalK = 10000.0,
breakDistance = 50.0,
tag = 1,
scaling = True
)
)
NRotBondPrms contains ve parameters: a bond tag specifying which bonded particles
will undergo this interaction, a unique name for the interaction group, the elastic stiness
(normalK) of the bonds, a boolean (scaling) to specify whether to scale the stiness
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 specied 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 o 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 conguration 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
Figure 2.4: A sequence of snapshots of a bouncing cube of particles (from gravity
cube.py)
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 ow 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 ow simulation will be used to illustrate how mesh walls are incorporated in
ESyS-Particle simulations.
Chapter 3
Advanced Models
3.1 Slope failure & hopper ow: variable particle
sizes, friction & mesh walls
So far we have encountered examples of all the basic elements making up an ESyS-Particle
simulation: a simulation object, particle assemblies, particle-particle interactions, body
forces, walls and particle-wall interactions. Along the way we have learnt how to output
simulation data to disk, make snapshots of particles in motion, and create re-usable
Runnables to achieve user-dened functions during simulations. The aim of this and
subsequent tutorials is to introduce a few more techniques commonly used in particle
simulations including frictional interactions, brittle failure of elastic bonds, movement of
walls, saving data using FieldSavers and the use of mesh walls to simulate complicated
boundary geometries.
This tutorial uses slope failure and hopper ow as the motivation for the introduction
of blocks comprised of particles of various sizes, frictional interactions between unbonded
particles, a simple method to simulate frictional walls and the inclusion of mesh walls.
We also discuss how symmetries in the problem to be solved can often be exploited to
improve simulation results without greatly increasing computation time. Slope failure
is an important problem in natural hazards (e.g. landslides), in handling of granular
materials (e.g. forming piles of sand or grains) and in minerals processing (e.g. the stock-
piling of ore in muckpiles). The study of hopper ow also plays its role in materials
handling (e.g. grain storage in silos). Along the way, the examples in this tutorial will
illustrate some of the key features of sand-piles, as well as introducing a few more ESyS-
Particle tricks and techniques.
3.1.1 Splash-down: collapse of a block of particles with variable
sizes
Until this point we have dealt predominantly with assemblies of particles with a constant
particle radius. Experience has shown that often the use of uniform particle sizes and
regular particle arrangements introduces artifacts such as preferential movement along
lattice planes. A simple way to remove such artifacts and to model more realistic granu-
lar materials is to use particle assemblies in which the positions and radii of particles are
selected randomly. ESyS-Particle provides a simple mechanism for constructing a rect-
angular block of particles with random positions and radii. The following code fragment
achieves this:
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)
To construct a random block of particles, we rst create a RandomBoxPacker by
providing a number of parameters dening the range of particle radii (minRadius and
maxRadius), the geometry of the block to ll with particles (bBox) and some additional
parameters related to the algorithm used to ll the block. We will discuss the particle
packing algorithm in a few moments. The example above will construct a cube 10 metres
on a side containing particles whose radii range from 0.2 metres to 0.5 metres. One corner
of the cube will be located at (X, Y, Z) = (5, 0, 5).
Algorithm for packing particles into a prescribed region
The RandomBoxPacker and some other ESyS-Particle Packer modules make use of an
iterative, geometrical space-lling algorithm to insert particles within a prescribed volume
(a rectangular prism in the case of RandomBoxPacker). The algorithm may be summarised
as follows:
1. Insert a number of seed particles at random locations within the volume ensuring
they do not overlap.
2. Identify 4 adjacent particles
(a) compute the centroid of the tetrahedron dened by the 4 particles
(b) compute the radius of a particle that touches all 4 particles
(c) if the radius of that particle is within the specied range and it is entirely
within the prescribed volume, insert the particle
3. Repeat step 2 until the number of failed insertion attempts reaches maxInsertFails
The tolerance parameter denes 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 tted together along these boundaries rather than being
tted to straight walls.
This algorithm for lling a volume with spherical particles has some distinct advantages
over some other methods but also some limitations. The algorithm requires no equilib-
riation simulation like some dynamical methods (e.g. expanding particle algorithms) to
CHAPTER 3. ADVANCED MODELS 37
achieve a close packing of relatively low porosity. The algorithm is also easily adapted
for lling quite arbitrary-shaped volumes, a feature exploited in the GenGeo add-on li-
brary for ESyS-Particle (which we will discuss later). On the downside, the user has
little control over the nal 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 nal
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.
Simulating collapse of a cube of unbonded particles
The aim of this example is to demonstrate the simulation of collapse of a cube of unbonded
particles under the action of gravity. Suppose one had a container of loose, dry powder
(such as sand or dirt) upturned on a table. When one lifts the container, one expects
the powder to fall outwards forming a pile. Rather than going straight to a complete
simulation of the process, we will progress in a number of stages so as to illustrate some
important points about both the dynamics of sand-piles and about tricks that can be
applied to simulate phenomena realistically using the Discrete Element Method.
Write an ESyS-Particle script comprised of a cube of particles sitting atop a wall
representing the table. Implement simple elastic repulsion between the particles, and
between particles and the wall. In addition, implement gravity and viscosity, and add a
Runnable to store snapshots every 1000 timesteps. Run the simulation for 50000 timesteps
with a timestep increment of 10
4
. If you need some help, refer to the slope fail.py
code-listing in Appendix A. Once you have run the simulation, you should have a sequence
of snapshots similar to those illustrated in Figure 3.1.
The most striking feature of the simulation results is the non-existence of a pile at
the end of the simulation. In fact, a movie of the simulation snapshots looks remarkably
like a uid splashing down on the table (hence the name of this example: Splash-down).
The reason for the discrepancy between the simulated results and our expected results
lies in the simplied physics we have implemented in the model. It is well-known that
the formation of sand-piles is governed by frictional interactions between the sand grains.
Indeed the steepness of the pile (known as the angle of repose) is directly related to the
coecient of inter-granular friction. In the next example we will attempt to improve our
model by incorporating frictional interactions between unbonded particles.
3.1.2 Sand-piles: adding frictional interactions
Frictional interactions between unbonded particles
As we saw in the previous example, the use of simple elastic repulsion between particles
is insucient to model formation of a sand-pile. The reason for this is postulated to be
the lack of frictional interactions between the particles. In this example we will replace
the NRotElastic Interaction Group with a new Interaction Group (called NRotFriction)
incorporating both elastic repulsion and frictional resistance between touching particles.
The following code-fragment illustrates how to incorporate frictional interactions in a
simulation:
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 rst parameter
(dynamicMu) denes the coecient of friction for touching particles whilst the second
parameter (shearK) denes the shear stiness at the contact point. When two particles
rst 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
coecient of friction, dynamic sliding commences (as the maximum shear force is governed
by the normal force and the friction coecient). This is a simple yet eective 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 rst 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.
Frictional interactions between particles and walls
As we saw in the previous example, adding friction between particles certainly helps to
form sand-piles but the lack of friction between particles and the table means the pile soon
collapses. Once particles hit the table they slide away on the frictionless surface rather
than remaining as support for particles rolling down on top. What we need is a frictional
surface representing the table-top. At the time of writing this tutorial, ESyS-Particle has
no Interaction Group specically for frictional interactions between particles and walls.
Consequently we need to think a little laterally to model a frictional surface.
One way would be to make the wall entirely out of a slab of stationary particles
that interact with the falling particles via a NRotFriction interaction. Ill leave it as
an exercise to try that yourself. Another alternative is to attach particles at the base of
the cube to the wall using the NRotBondedWall interaction group. Particles attached to
the wall will be constrained to remain in place, oscillating around a point if large forces
are applied by surrounding particles. The main reason for choosing this approach is to
demonstrate another useful feature of ESyS-Particle: tagging of groups of particles so we
selectively apply interactions to those particles. This is a handy feature that will be useful
in a number of dierent scenarios.
We aim to make use of the NRotBondedWall Interaction Group to bond particles
to the wall with unbreakable elastic bonds. The code fragment for implementing such
interactions is as follows:
sim.createInteractionGroup (
CHAPTER 3. ADVANCED MODELS 40
NRotBondedWallPrms (
name = "floor_bonds",
wallName = "floor",
normalK = 10000.0,
particleTag = 12321
)
)
The rst three parameters should now be familiar. The fourth parameter (particleTag)
species 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 specic particles to the wall, we need to assign a specic 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 modied script is called slope friction
floor.py in Appendix A. Having made the necessary modications, 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 owed 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 eectively (and quite
simply). The simulation also further demonstrates that a sand-pile requires a frictional
oor 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 oor (from slope friction floor.py)
Making use of symmetry to improve simulation results
Often the physical phenomenon one wishes to model contains geometrical symmetries
that can be exploited to improve simulation results without needing to increase the total
number of particles. For example, in our sand-pile simulations, the particles are initially
arranged in a cubic volume which has three symmetry axes parallel with the sides of the
cube. The nal sand-pile is approximated by a cone which has circular symmetry around
the (vertical) axis of the cone. In order to model the transformation of the cubic volume
of particles into a conical pile, we really dont need to model the entire volume due to
these inherent symmetries. A perfectly reasonable solution would be to model only one
quarter of the cube collapsing to form one quarter of a cone. We can then rotate the
nal solution about the symmetry axis to obtain a reasonable representation for the nal
sandpile shape. The advantage is that we can model the process at higher resolution
without increasing the total number of particles (and hence the time taken to run the
simulation).
One needs to exercise some caution however to ensure that the physics along symmetry
cut-planes is accurately modelled. Typically the dynamics either side of a symmetry cut-
plane are the mirror image of each other, known as even symmetry. To achieve this, in
most cases the use of frictionless walls along cut-planes achieves the desired result. To
see how a quarter-symmetry simulation works, make the following modications to your
previous script (slope friction floor.py):
1. Add a wall located at (X, Y, Z) = (5, 0, 0) with a normal in the positive X-direction
(i.e. n = (1, 0, 0)). This wall will be known as the left wall.
2. Add a second wall located at (X, Y, Z) = (0, 0, 5) with a normal in the positive
Z-direction (i.e. n = (0, 0, 1)). This wall will be known as the back wall.
CHAPTER 3. ADVANCED MODELS 42
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 eectively
four times that of the previous example. Although some particles still slide away on the
frictionless oor, we have improved the spatial resolution of our sand-pile model quite
considerably without sacricing 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 eectively semi-innite in one coordinate direction.
3.1.3 Hopper ow: Using quarter symmetry and mesh walls
Now that youve discovered the joys of slope collapse, there are really only a couple of
steps to take this simulation and use it to study hopper ows. The ow of material in
CHAPTER 3. ADVANCED MODELS 43
silos encounters many problems in the real world. These include rat-holing, stalling of
ow around the exit, uneven draw proles and unexpected segregation of ner material to
name a few. The study of granular ow 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 ow is another application that benets 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.
The mesh wall le format
So far we have only used planar walls in our simulations. Planar walls, by their denition,
are innite in length, making it dicult to simulate problems that require complex wall
shapes or walls with holes. Mesh walls overcome this problem but are slightly more
complicated to implement in simulations. ESyS-Particle uses a triangulated mesh format
to dene piecewise segments of a wall. Generation of these meshes can be done through
most CAD packages but require a little work to convert into the ESyS-Particle mesh
format. An example mesh le is shown below for an L shaped oor mesh that we will
use in the upcoming example:
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 le commences with a format header Triangle; the number after 3D-Nodes
species how many nodal points exist in the mesh followed by the specication of locations
of these points in the format:
ID Dummy Tag X Y Z
The number after Tri3 species 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 specication, regarding which see Figure 3.5. The tri-elements link the nodes
in the following manner:
ID Tag Point1 Point2 Point3
Using mesh walls in hopper ow simulations
Save the triangle mesh wall specication above in a le called floorMesh.msh. We can
now read the mesh wall into a simulation object using the following code fragment:
CHAPTER 3. ADVANCED MODELS 44
Figure 3.5: Specication of mesh nodes for wall normals
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 oor 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 par-
ticles 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:
#add particles to simulation one at a time,
#tagging those in layers of 2m
for pp in geoRandomBlock_particles:
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 particle to the simulation object
The new tagging code will tag particles with one of two dierent 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 ow of particles later.
Congratulations, you have now made all of the changes to the code required to do a
hopper ow 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 exible feature of ESyS-Particle that
allow complex shapes and interactions to be simulated. Discussing the model above, two
unrealistic simplications have been used that results in hopper ow dynamics somewhat
dierent 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 ow around the outlet. The second simplication
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 ow
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 ow 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
3.2 Uniaxial compression simulations: moving walls,
model calibration and FieldSavers
Quasi-static uniaxial compression of rock samples is a common laboratory technique for
measuring the macroscopic properties of rocks such as Youngs modulus, Poissons ratio
and the unconned compressive strength. In uniaxial compression experiments, a sample
of rock is slowly compressed by a piston until failure occurs. By measuring the force
applied to the sample and the strain, one can measure the Youngs modulus of the rock
sample. The peak stress at which failure of the sample occurs is known as the Unconned
Compressive Strength (UCS), an important measure of the strength of rocks.
In this tutorial we will describe how to perform uniaxial compression simulations using
ESyS-Particle. Such simulations provide a means to measure the equivalent macroscopic
properties of synthetic rock samples and are an important tool for calibrating Discrete
Element models. When discussing calibration it is important to distinguish between the
microphysical parameters dening interaction laws (e.g. elastic stinesses, coecients of
friction, etc.) and the macroscopic elastic properties (Youngs modulus, macroscopic
friction angle, etc.). The later are a function not only of the microphysical parameters,
but also the geometrical properties of the network of bonded and unbonded interactions
within the particle assembly. There is often no known analytical relationship between
the microphysical parameters and the macroscopic properties (except in special cases of
regular packings of equal-sized particles). To overcome this, it is common procedure to
conduct a series of uniaxial compression simulations to tune the microphysical parameters
until desired macroscopic properties are obtained.
To simulate rock breakage under compressive loads, we will need to introduce a number
of new ESyS-Particle techniques. These include:
1. implementation of cementatious (rotational) elastic-brittle bonds and rotational fric-
tion interactions,
2. moving walls via a Runnable, and
3. selective storage of simulation data using FieldSavers.
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.
3.2.1 Uniaxial compression simulations
In this example we will discuss how to implement a uniaxial compression simulation using
ESyS-Particle. The model will consist of a rectangular prism of particles sandwiched
between two piston walls which will be compressed at constant speed. We will utilise
FieldSavers to monitor the positions of, and forces acting on, the walls, as well as the
total strain energy stored in bonds between particles and the number of broken bonds.
These simulation data will then be used to measure the macroscopic elastic properties of
the particle model. Full code-listings for uniaxial compression simulations are available
in Appendix A.
CHAPTER 3. ADVANCED MODELS 48
Initialising the simulation object
Every ESyS-Particle simulation requires a LsmMpi simulation object. This object serves
as a container to which we can add particles, walls, interactions and other modules for
storing data or moving walls. The following code-fragment loads the ESyS-Particle Python
modules and initialises the simulation object:
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
#instantiate a simulation object:
sim = LsmMpi (numWorkerProcesses = 1, mpiDimList = [1,1,1])
#initialise the neighbour search algorithm:
sim.initNeighbourSearch (
particleType = "RotSphere",
gridSpacing = 5.0000,
verletDist = 0.08000
)
#set the number of timesteps and timestep increment:
sim.setNumTimeSteps (250000)
sim.setTimeStepSize (1.0000e-06)
#specify the spatial domain for the simulation
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain (domain)
Most of these commands should now be familiar from previous tutorials. We rst 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.
Creating a block of variable-sized particles
Having initialised the simulation object, we need to insert particles and bond them to-
gether. For rock breakage simulations, the best results are obtained using blocks of
particles with variable radii and random locations. We will use the RandomBoxPacker
rst encountered in the previous tutorial:
#create a prism of spherical particles:
geoRandomBlock = RandomBoxPacker (
minRadius = 0.400,
maxRadius = 2.0000,
cubicPackRadius = 2.2000,
maxInsertFails = 5000,
bBox = BoundingBox(
CHAPTER 3. ADVANCED MODELS 49
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()
#add the particles to the simulation object:
sim.createParticles(geoRandomBlock_particles)
#bond particles together with bondTag = 1:
sim.createConnections(
ConnectionFinder(
maxDist = 0.005,
bondTag = 1,
pList = geoRandomBlock_particles
)
)
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 nal 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 nd 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 rst encountered the ConnectionFinder
in gravity cube.py when we created a bonded cube of particles to bounce on the oor.
Adding walls to the simulation object
Next we need to add two walls to the simulation object. These walls will serve as pis-
tons for compressing the rock sample. We will add one wall below the sample (the
bottom wall) and another atop the sample (the top wall):
#create a wall at the bottom of the model:
sim.createWall (
name = "bottom_wall",
posn = Vec3(0.0000, 0.0000, 0.0000),
normal = Vec3(0.0000, 1.0000, 0.0000)
)
#create a wall at the top of the model:
sim.createWall (
name = "top_wall",
posn = Vec3(0.0000, 20.0000, 0.0000),
normal = Vec3(0.0000, -1.0000, 0.0000)
)
CHAPTER 3. ADVANCED MODELS 50
Simply adding walls to the simulation object is insucient. We must also dene in-
teractions between the walls and particles. For basic uniaxial compression simulations,
repulsive elastic interactions are sucient. 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:
#specify elastic repulsion from the bottom wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "bottom_wall_repel",
wallName = "bottom_wall",
normalK = 100000.0
)
)
#specify elastic repulsion from the top wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "top_wall_repel",
wallName = "top_wall",
normalK = 100000.0
)
)
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 var-
ious data about interactions using FieldSavers. Elastic repulsion of particles from walls
requires specication of a single microphysical parameter, the elastic stiness (normalK).
For this simulation we set the elastic stiness equal to 100000 N/mm.
Rotational bonds and frictional interactions
In previous examples we have encountered two types of particle interactions: NRotElastic
and NRotFriction interactions. Both of these interactions are designed for particles with
only three (translational) degrees of freedom (known as NRotSpheres) and are suitable
for granular ow of individual particles or aggregates. To simulate elastic-brittle fail-
ure of rocks, more sophisticated particle-pair interactions are required. In particular, we
require particle-pair interactions that incorporate both translational and rotational de-
grees of freedom. As illustrated in Figure 3.7, two bonded particles may undergo normal
and shear forces, as well as bending and twisting moments. Bonds designed to impart
such forces and moments are known as cementatious bonds (or in ESyS-Particle parlance
BrittleBeamPrms interactions).
There is also a FrictionPrms interaction specically designed for frictional interac-
tions between unbonded rotational particles. Unlike the non-rotational equivalent, rota-
tional frictional interactions impart a torque to both particles, causing the particles to
rotate relative to each other when in frictional contact.
The following code-fragment denes the microphysical parameters of rotational bonds:
#create rotational elastic-brittle bonds between particles:
pp_bonds = sim.createInteractionGroup (
CHAPTER 3. ADVANCED MODELS 51
Figure 3.7: Diagram illustrating the forces
and moments between particles bonded via
rotational elastic-brittle bonds
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 inuences 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
dene the cohesive strength and friction angle of bonds.
When a bond between two particles breaks, we need to specify the type of unbonded
interactions the particles will experience should they come into contact. Since a broken
bond represents a fracture surface, it is appropriate to specify frictional interactions be-
tween unbonded particles. The following code-fragment implements frictional interactions
between unbonded, touching particles:
#initialise frictional interactions for unbonded particles:
sim.createInteractionGroup (
FrictionPrms(
CHAPTER 3. ADVANCED MODELS 52
name="friction",
youngsModulus=100000.0,
poissonsRatio=0.25,
dynamicMu=0.4,
staticMu=0.6
)
)
Rotational frictional interactions are dened by a microscopic Youngs modulus (youngs
Modulus) and Poissons ratio (poissonsRatio) and two microscopic coecients of friction.
Typically the Youngs modulus and Poissons ratio for FrictionPrms interactions are set
equal to their BrittleBeamPrms counterparts. The staticMu coecient of friction is
applied when two particles are in static frictional contact, i.e., prior to the rst time
the frictional sliding criterion is met. Thereafter the dynamicMu coecient of friction is
applied. By setting dynamicMu < staticMu, one can simulate the physical observation
that the frictional force required to maintain sliding is less than the force necessary to
initiate sliding.
Finally, we must inform the simulation object that any given particle-pair undergoes
either bonded interactions or frictional interactions but not both. This is achieved by
specifying an exclusion between the two interaction groups:
#create an exclusion between bonded and frictional interactions:
sim.createExclusion (
interactionName1 = "pp_bonds",
interactionName2 = "friction"
)
Implementation of viscous damping
Uniaxial compression experiments are usually conducted in the so-called quasi-static
regime. In other words, external loads are applied slowly compared with the compressional
wavespeed of the sample. Any acoustic emissions generated during fracturing dissipate
rapidly compared with the duration of the experiment. To simulate these conditions,
we must also incorporate two body forces designed to attenuate translational and ro-
tational oscillations. In previous examples we encountered the LinDamping body force.
In the uniaxial compression simulations we will use both LinDamping and RotDamping,
the later being designed to attenuate rotational oscillations. The two damping forces are
implemented thus:
#add translational viscous damping:
sim.createInteractionGroup (
LinDampingPrms(
name="damping1",
viscosity=0.002,
maxIterations=50
)
)
#add rotational viscous damping:
sim.createInteractionGroup (
RotDampingPrms(
CHAPTER 3. ADVANCED MODELS 53
name="damping2",
viscosity=0.002,
maxIterations=50
)
)
The viscosity coecients are chosen to be small so that damping has little eect on the
elastic response of the simulated rock sample but it is sucient to attenuate unwanted
oscillations.
Implementation of movable walls: the WallLoader Runnable
Only one component remains to be added to the uniaxial compression simulation: a
method to move the two walls at constant speed. There are a couple of ways this may be
implemented: either as a subroutine that is called each timestep of the simulation or as
a reusable Runnable module. We previously encountered Runnables in the rst tutorial.
The use of a Runnable is considered superior as the module can be re-used in subsequent
simulations whenever movable walls are required.
There are two steps involved in implementing and using a Runnable:
1. Write a script containing the implementation of the Runnable, and
2. Add the runnable into the simulation container.
The following code (entitled WallLoader.py in Appendix A) implements a Runnable
to move a given wall with a specied velocity. To achieve this, the Runnable must be
passed a reference to the simulation object, a wall name, a velocity and two additional
parameters: startTime and rampTime. Experience has shown that it is best to gradually
increase the wall speed from zero to the desired value over a few hundred timesteps. The
rampTime parameter species the number of timesteps during which the wall accelerates.
The velocity of the wall increases linearly over that number of timesteps. The other
parameter (startTime) allows the user to commence moving the wall after a specied
number of timesteps.
The WallLoader Runnable has two subroutines:
init () to initialise the Runnable and store parameter values, and
run() which moves the wall and is called once each timestep of the simulation.
The implementation of the WallLoader Runnable is as follows:
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
class WallLoaderRunnable (Runnable):
def __init__ (self,
LsmMpi=None,
wallName=None,
vPlate=Vec3(0,0,0),
startTime=0,
rampTime = 200):
"""
CHAPTER 3. ADVANCED MODELS 54
Subroutine to initialise the Runnable and store parameter values.
"""
Runnable.__init__(self)
self.sim = LsmMpi
self.wallName = wallName
self.Vplate = vPlate
self.dt = self.sim.getTimeStepSize()
self.rampTime = rampTime
self.startTime = startTime
self.Nt = 0
def run (self):
"""
Subroutine to move the specified wall. After self.startTime
timesteps, the speed of the wall increases linearly over
self.rampTime timesteps until the desired wall speed is achieved.
Thereafter the wall is moved at that speed.
"""
if (self.Nt >= self.startTime):
#compute the slowdown factor if still accelerating the wall:
if (self.Nt < (self.startTime + self.rampTime)):
f = float(self.Nt - self.startTime) / float(self.rampTime)
else:
f = 1.0
#compute the amount by which to move the wall this timestep:
Dplate = Vec3(
f*self.Vplate[0]*self.dt,
f*self.Vplate[1]*self.dt,
f*self.Vplate[2]*self.dt
)
#instruct the simulation to move the wall:
self.sim.moveWallBy (self.wallName, Dplate)
#count the number of timesteps completed thus far:
self.Nt += 1
Copy the code above into a text le called WallLoader.py and save it in the same di-
rectory as the le 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:
1. import the Runnable at the start of the script like so:
from WallLoader import WallLoaderRunnable
2. add a WallLoaderRunnable for each of the two piston walls:
#add a wall loader to move the top wall:
wall_loader1 = WallLoaderRunnable(
CHAPTER 3. ADVANCED MODELS 55
LsmMpi = sim,
wallName = "top_wall",
vPlate = Vec3 (0.0, -0.125, 0.0),
startTime = 0,
rampTime = 50000
)
sim.addPreTimeStepRunnable (wall_loader1)
#add a wall loader to move the bottom wall:
wall_loader2 = WallLoaderRunnable(
LsmMpi = sim,
wallName = "bottom_wall",
vPlate = Vec3 (0.0, 0.125, 0.0),
startTime = 0,
rampTime = 50000
)
sim.addPreTimeStepRunnable (wall_loader2)
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 signicantly higher than that typically used in laboratory uniaxial
compression experiments, it is suciently 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 rst 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:
#execute the simulation:
sim.run()
If you have been following the previous tutorials, you may notice that we have not specied
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 specic interest
during uniaxial compression simulations.
3.2.2 Measurement of macroscopic elastic properties
One of the primary reasons for conducting uniaxial compression simulations is to measure
the Youngs modulus and Unconned Compressive Strength (UCS) of the rock sample.
Both of these macroscopic properties can be obtained from a stressstrain curve, as il-
lustrated in Figure 3.8. Youngs modulus is dened as the slope of the linear section
of the stressstrain curve, whilst the UCS is the peak value of the stress. In order to
measure these quantities in a simulation, we must construct the stressstrain curve for
the simulation.
Suppose the net restoring forces (at time t) that particles apply to the top and bottom
walls are

F
(t)
(t) and

F
(b)
(t) respectively, and the unit normal vector of the walls is n
(t/b)
.
Then the stress exerted on the walls by the particle assembly is:
CHAPTER 3. ADVANCED MODELS 56
Figure 3.8: Diagram illustrating a typical stress-strain curve and how to measure Youngs
modulus (E) and the unconned compressive strength (UCS) from such a curve

Y Y
(t) =

F
(t)
. n
(t)
+

F
(b)
. n
(b)
2A
c
, (3.2)
where A
c
is contact area between a wall and the particle assembly. Since the peak stress
is reached for relatively small axial strain (only a few % usually), the contact area can be
approximated by the undeformed area of base of the particle assembly (in our example,
that would be 10m10m = 100m
2
).
Computing the total strain is also relatively straightforward. Let

X
(t)
(t) and

X
(b)
(t)
be the positions of the top and bottom walls at time t. The strain
Y Y
(t) is given by:

Y Y
(t) =

X
(t)
(0)

X
(b)
(0)

X
(t)
(t)

X
(b)
(t)

. y

X
(t)
(0)

X
(b)
(0)

. y
(3.3)
= 1

X
(t)
(t)

X
(b)
(t)

. y

X
(t)
(0)

X
(b)
(0)

. y
(3.4)
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 les and construct the stressstrain curve for a simulation.
Storing wall positions and forces
ESyS-Particle includes a group of modules called FieldSavers designed to store specic
simulation data to disk. FieldSavers are closely related to the CheckPointer encoun-
tered in the rst tutorial, the main dierence being that FieldSavers store only specic
data rather than all of the state variables of the particles. FieldSavers can also be used
CHAPTER 3. ADVANCED MODELS 57
to store data on particles (such as position or kinetic energy), interactions (such as po-
tential 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:
#create a FieldSaver to wall positions:
posn_saver = WallVectorFieldSaverPrms(
wallName=["bottom_wall", "top_wall"],
fieldName="Position",
fileName="out_Position.dat",
fileFormat="RAW_SERIES",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=10
)
sim.createFieldSaver(posn_saver)
This code-fragment should be inserted before sim.run() is called. WallVectorFieldSav
erPrms takes a number of parameters. These are:
wallName a list of the names of walls whose position you wish to save
fieldName the data eld to store (in this case Position)
fileName the name of the text le in which to store the data
fileFormat the output format (RAW SERIES creates an ASCII text le)
beginTimeStep the timestep number to commence storing data
endTimeStep the timestep number to conclude storing data
timeStepIncr the number of timesteps to wait between storing a datum
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:
#create a FieldSaver to wall forces:
force_saver = WallVectorFieldSaverPrms(
wallName=["bottom_wall", "top_wall"],
fieldName="Force",
fileName="out_Force.dat",
fileFormat="RAW_SERIES",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=10
)
sim.createFieldSaver(force_saver)
CHAPTER 3. ADVANCED MODELS 58
Measurement of Youngs modulus and unconned compressive strength
Once you have written all the code-fragments above into a text le (called rot com
press.py) and created a WallLoader.py le containing the implementation of the Wall
Loader Runnable, execute a simulation by typing the following at the command prompt:
$ mpirun -np 2 which esysparticle rot_compress.py
The simulation may take some time so feel free to go grab a coee while you wait!
When the simulation is completed, two text les should have been written into the
current working directory, out Position.dat and out Force.dat. If you examine one of
these les with a text editor, the rst 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 le 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 rst 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 les 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.
from math import *
posnfile = open("out_Position.dat","r")
posn = posnfile.readlines()
posnfile.close()
forcefile = open("out_Force.dat","r")
force = forcefile.readlines()
forcefile.close()
for i in range (len(posn)):
Y_bottom = float(posn[i].split()[1])
Y_top = float(posn[i].split()[4])
F_bottom = float(force[i].split()[1])
F_top = float(force[i].split()[4])
stress = (F_bottom - F_top)/100.0
strain = 1.0 - (Y_top - Y_bottom)/20.0
print strain,stress
CHAPTER 3. ADVANCED MODELS 59
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 le 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:
$ python make_stress_strain.py > stress_strain.dat
A new text le (called stress strain.dat) will be created, the rst 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 unconned 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 elds provide infor-
mation on the internal deformation of the simulated rock sample, quantities not usually
directly amenable to measurement in laboratory experiments.
Storing information on particles and bonds: kinetic energy, potential energy
and number of bonds
In the previous section we examined how to output wall forces and positions using
FieldSavers. Both these data elds are examples of measurable quantities, i.e., quantities
that can be directly measured in equivalent laboratory experiments. Comparison of mea-
surable quantities from simulations and experiments is a useful technique for validating
and calibrating Discrete Element models. However, the primary advantage of computer
simulations in scientic research is the ability to examine internal dynamics that are typ-
ically not amenable to direct observation in the laboratory or eld. In this section we
CHAPTER 3. ADVANCED MODELS 60
examine three quantities that provide insight on the internal deformation within rock
samples subjected to uniaxial compressive loads.
The rst 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., E
k
=

i
1
2
m
i
v
2
i
. It
is frequently useful to measure the total kinetic energy in simulations, particularly if
one suspects a simulation is numerically unstable. An unbounded growth in the total
kinetic energy over time is a good indication that a simulation is not working correctly.
The following code-fragment initialises a ParticleScalarFieldSaver to store the total
kinetic energy each timestep:
#create a FieldSaver to store the total kinetic energy of the particles:
sim.createFieldSaver (
ParticleScalarFieldSaverPrms(
fieldName="e_kin",
fileName="ekin.dat",
fileFormat="SUM",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=1
)
)
Most of the FieldSaver parameters should now be familiar. A new fileFormat called
SUM is used here. This le format stores the sum of the kinetic energy of each particle. The
output format is an ASCII text le with the total kinetic energy written once per timestep.
A number of other elds 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 par-
ticles. The total strain energy is simply the sum of the potential energies of the rota-
tional bonds connecting each particle-pair. The following code-fragment initialises an
InteractionScalarFieldSaver to store the total strain energy each timestep:
#create a FieldSaver to store potential energy stored in bonds:
sim.createFieldSaver (
InteractionScalarFieldSaverPrms(
interactionName="pp_bonds",
fieldName="potential_energy",
fileName="epot.dat",
fileFormat="SUM",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=1
)
)
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
dening rotational bonds between particles). Once again we specify a SUM fileFormat
so that the total potential energy is output to an ASCII text le 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)
Another extremely useful quantity to monitor in elastic-brittle simulations is the num-
ber of broken bonds, as this is a measure of the amount of damage the rock sample has
suered due to the external load. Another InteractionScalarFieldSaver is used to
output the total number of bonds each timestep. The relevant code-fragment is as fol-
lows:
#create a FieldSaver to store number of bonds:
sim.createFieldSaver (
InteractionScalarFieldSaverPrms(
interactionName="pp_bonds",
fieldName="count",
fileName="nbonds.dat",
fileFormat="SUM",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=1
)
)
The main dierence 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.11: Time-series of percentage of bonds broken during a uniaxial compression
simulation (from rot compress.py)
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
signicant fraction of bonds break before the peak stress is reached. In other words, the
sample undergoes signicant irreversible internal damage prior to reaching the unconned
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 les 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 specic 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 les to permit visualisation and analysis of data that is not easily
computed during simulations. We will demonstrate how to convert CheckPoint les 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
3.3 Post-processing and data visualisation
ESyS-Particle is primarily designed as a high-performance parallel DEM simulation en-
gine. The Python API makes designing and executing dierent simulations relatively
simple and straightforward, however the data output mechanisms (FieldSavers and
CheckPointers) are relatively basic. For most applications, post-processing of simulation
output les will be necessary to obtain useful results. ESyS-Particle is packaged with a
few tools designed to aid in the post-processing of simulation data to obtain particular
results or convert the output into formats that can be visualised using freely available
third-party visualisation tools (such as ParaView and VisIt).
In this tutorial, we will discuss some of these post-processing tools and how to use them
for more advanced visualisation of simulation results. By way of motivation we will use
the uniaxial compression simulation (rot compress.py) discussed in the previous tutorial.
By adding a CheckPointer to our simulation and post-processing the checkpoint les, we
will be able to:
interactively visualise a variety of simulation data,
calculate the size and shape of rock fragments generated, and
visualise the locations of fractures formed during the compression test.
We rst encountered the CheckPointer in Chapter 3. CheckPointers provide a con-
venient way to output data from ESyS-Particle simulations. In combination with the
post-processing tools described below (amongst others), quite advanced visualisation and
analysis of simulation data may be achieved. To begin, add a CheckPointer to the
rot compress.py script (just before the sim.run() subroutine call), then re-run the sim-
ulation:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "snapshot",
beginTimeStep = 0,
endTimeStep = 250000,
timeStepIncr = 1000
)
)
A series of CheckPoint les will now have been written to the working directory, each
beginning with the prex snapshot. These checkpoint les 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 les into a format suitable for interactive
visualisation,
2. grainextract: a tool that identies clusters of particles bonded together, and
3. fracextract: a tool for visualising the location of broken bonds in brittle failure
simulations.
CHAPTER 3. ADVANCED MODELS 65
Figure 3.13: An example of an ESyS-Particle simulation visualised using ParaView.
3.3.1 Interactive visualisation of simulation data
Perhaps the most versatile of ESyS-Particles post-processing tools is dump2vtk. This
tool will convert a sequence of CheckPoint les (also known as dump les) into VTK
unstructured mesh les. VTK (short for Visualisation Tool Kit) is a popular library for
three-dimensional post-processing and visualisation of scientic datasets. Although de-
signed primarily for advanced visualisation and analysis of mesh-based datasets (e.g. from
nite dierence or nite element software), VTK has quite some utility for post-analysis
of DEM simulation data also. Third-party visualisation software (such as ParaView and
VisIt) provide interactive 3D visualisation capabilities that make these packages (in com-
bination with dump2vtk) a powerful addition to an ESyS-Particle modellers arsenal.
For this tutorial, we will focus on visualisation using ParaView to simplify discussion.
VisIt is an equally good choice for visualising ESyS-Particle simulations, if you prefer it
over ParaView. The rst step to interactively visualise ESyS-Particle simulation data
is to convert the CheckPoint les into VTK unstructured mesh les. ESyS-Particles
dump2vtk post-processing tool is designed for this purpose.
dump2vtk: convert checkpoint les to VTK les
The following shell command will convert all of the rot compress.py CheckPoint les:
CHAPTER 3. ADVANCED MODELS 66
$ dump2vtk -i snapshot -o vtk_snaps_ -rot -t 0 251 1000
Having successfully executed this command, 251 new les will be written to the directory
containing the CheckPoint les. A partial directory listing of these les 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 les are in a suitable format for opening from within ParaView (File |
Open..). Having opened the sequence of VTK les, you will be presented with a graphical
window similar to Figure 3.13.
Interactive visualisation using ParaView
The default graphical view in ParaView shows only the bonds linking particles, with
the colours representing the radius of the particle residing at the intersection points of
bonds. The VTK output les from dump2vtk contain a number of dierent scalar and
vector elds that may be selected for visualisation via the Display tab in the Object
Inspector pane to the left of the main ParaView window. The scalar and vector elds
available for visualisation are:
scalar elds:
radius: the radius of individual particles,
particleTag: the tag assigned to each particle,
Id: the unique identity number of each particle,
bondTag: the tag assigned to individual bonds,
bondStrain: an indication of the amount of strain stored in each bond,
proc id: the unique worker process identity number for visualising parallel
subdomain decomposition.
vector elds:
velocity: the current velocity of each particle,
angular velocity: the current angular velocity of each particle,
displacement: the total displacement of each particle since the simulation
commenced, and
initial position: the initial position of each particle.
CHAPTER 3. ADVANCED MODELS 67
Figure 3.14: Examples of visualisation using Glyphs in ParaView. A) Particles repre-
sented as sphere glyphs, coloured by the X-component of velocity; B) Particle velocities
represented as arrow glyphs, coloured by the speed of each particle.
In many cases, useful information can be gained by simply colouring the bonds ac-
cording to one of the scalar elds above, or the magnitude of one of the vector elds. In
other instances it is useful to use a so-called Glyph lter to visualise, for example, the
velocity eld as an assembly of arrows. Perhaps the most useful Glyph lter is the Sphere
lter. This lter 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 elds listed above. Examples of visualising the particle assembly and a velocity eld
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 les in temporal order. Clicking on the Play symbol will
automatically step through all the (251) snapshot les 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.
3.3.2 Calculating the number and size of rock fragments
grainextract: analysing rock fragments
Visualising rock fragments using ParaView
3.3.3 Visualising cracks formed during fracture simulations
fracextract: identifying locations of broken bonds
Visualising fractures using ParaView
Whats Next?
In this tutorial some of the ESyS-Particle post-processing tools were introduced. One of
these tools (dump2vtk) converts CheckPointer output les into VTK les, a popular 3D
CHAPTER 3. ADVANCED MODELS 68
visualisation data format. Having converted checkpoint les 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 nal 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, nding 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).
3.4 Annular shear cells: quasi-static 2D simulations
with periodic boundaries and servo walls
In Chapter 6, uniaxial compression simulations were introduced for the purpose of cal-
ibrating the microphysical parameters of DEM models so that the macroscopic elastic
properties match those of brittle-elastic materials such as rocks. Whilst uniaxial compres-
sion simulations (and their companions, tension and triaxial compression simulations) are
quite useful for calibrating a DEM model to simulate brittle failure, such simulations are
not as useful if one wishes to calibrate the model to simulate, for example, ow of granular
material.
One of the key macroscopic properties of granular media is the bulk friction coecient,
dened as the eective frictional resistance of a volume of granular material under shear
loading. In the laboratory, the bulk friction coecient is measured via either direct shear
tests or annular shear cell tests. We will focus upon the later in this chapter. An annular
shear cell consists of two metal rings, the bottom of which contains a groove within which
is placed granular material (e.g. sand, gravel or powder). The top ring is then lowered
onto the bottom ring and a series of weights and pulleys are used to maintain a constant
vertical pressure on the ring of granular material. Subsequently the bottom ring is rotated
at constant speed, thus shearing the granular material to a desired total shear strain. By
measuring the force required to maintain a constant shearing rate, experimentalists can
estimate the eective bulk friction coecient of the granular material.
In the following, we will discuss how to construct a two-dimensional version of the
annular shear cell test using ESyS-Particle and demonstrate how the bulk friction co-
ecient may be calculated in shear cell simulations. The main purpose here is not to
describe a production-ready DEM annular shear cell model suitable for calibration, but
rather to elucidate a number of useful techniques in DEM modelling and how these are
CHAPTER 3. ADVANCED MODELS 70
implemented in ESyS-Particle simulations. As it happens, the annular shear cell is an
ideal application with which to introduce these techniques within a practical context. The
key techniques we will discuss are:
how to restrict ESyS-Particle to two-dimensional computations,
how to implement periodic (or circular) boundaries in one coordinate direction,
how to conduct quasi-static simulations, and
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:
1. large viscous damping to remove elastic waves, and
2. higher than normal particle densities to ensure particle accelerations remain rela-
tively small throughout the simulation.
The complete code-listing for the shear cell simulations may be found in Appendix A.1.17.
3.4.1 Two-dimensional computations and periodic boundaries
For a number of applications, two-dimensional DEM simulations are sucient to obtain
useful results. Although ESyS-Particle is primarily designed with large, three-dimensional
simulations in mind, it is relatively simple to instruct ESyS-Particle to ignore compu-
tations in the Z-direction, eectively reducing the simulation to two dimensions. The
LsmMpi.force2dComputations(..) subroutine call achieves this. This subroutine call
should be made very soon after initialising the simulation object (LsmMpi) and before
setting the spatial domain of the simulation. The following code-fragment illustrates the
use of LsmMpi.force2dComputations(..):
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
#create a simulation container object:
# N.B. there must be at least two sub-divisions
# in the X-direction for periodic boundaries
sim = LsmMpi (numWorkerProcesses=2, mpiDimList=[2,1,1])
sim.initNeighbourSearch (
particleType = "NRotSphere",
CHAPTER 3. ADVANCED MODELS 71
gridSpacing = 2.5,
verletDist = 0.5
)
#specify the number of timesteps and timestep increment:
sim.setNumTimeSteps(100000)
sim.setTimeStepSize(0.001)
#enforce two-dimensional computations:
sim.force2dComputations (True)
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:
#specify the spatial domain and direction of periodic boundaries:
domain = BoundingBox ( Vec3 (0,0,0), Vec3 (10,10,0) )
sim.setSpatialDomain (
bBox = domain,
circDimList = [True, False, False]
)
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 par-
ticles 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 bound-
aries. The following code-fragment illustrates:
#construct a rectangle of unbonded particles:
packer = RandomBoxPacker (
minRadius = 0.1,
maxRadius = 0.5,
cubicPackRadius = 2.2,
maxInsertFails = 1000,
bBox = BoundingBox(
Vec3(0.0, 0.0,0.0),
CHAPTER 3. ADVANCED MODELS 72
Vec3(10.0, 10.0, 0.0)
),
circDimList = [True, False, False],
tolerance = 1.0e-5
)
packer.generate()
particleList = packer.getSimpleSphereCollection()
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.
3.4.2 Quasi-static simulations: local damping and high densities
Annular shear cell experiments in the laboratory are typically conducted under so-called
quasi-static conditions. Mathematically, quasi-static conditions imply that particle ac-
celerations are negligible and, hence, inertial eects (such as elastic wave propagation)
are neglected. ESyS-Particle employs an explicit nite-dierence time-integration scheme
in which particle velocities and positions are updated by computing the instantaneous
acceleration of each particle (via Newtons Second Law). Such a time-integration scheme
is only suitable for dynamic conditions in which the accelerations are non-zero. If all
particle accelerations were zero, no stationary particles would move and non-stationary
particles would move at constant speed (Newtons First Law).
One way to simulate quasi-static conditions in a DEM model would be to change
the time-integration scheme to an implicit (typically iterative) scheme in which particle
positions and forces are repeatedly updated until all forces are negligible. To implement
such a scheme in ESyS-Particle would require signicant re-factoring of the DEM engine
and is likely to be quite computationally inecient. An alternative approach that is often
adopted by DEM practitioners involves the use of two numerical approximations that
together achieve quasi-static conditions.
The rst numerical approximation is known as the high density approximation. It
involves assigning unrealistically large densities (or masses) to all particles. Since the
instantaneous acceleration of a particle is inversely proportional to its mass (a = F/m),
a larger mass results in a smaller acceleration, for a given net force acting on a particle.
Here the particle mass is being used as a type of penalty factor to reduce the amplitudes
of particle accelerations. However, given that the particle accelerations will never be zero,
some inertial eects will be expected to remain.
The second numerical approximation, large articial damping, attempts to reduce the
impact of these inertial eects. By utilising a relatively large amount of viscous damping,
elastic waves can be eciently reduced in amplitude over a few time-steps. In this way
inertial eects can also be managed to achieve eectively quasi-static conditions in the
DEM simulations.
To implement the high density approximation in ESyS-Particle simulations, we must
prescribe large particle densities. The LsmMpi.setParticleDensity(..) subroutine
achieves this. As this subroutine sets the density of all particles with a given tag, we must
rst assign a tag to each particle before insertion into the simulation object. Later we will
need to bond a layer of particles top and bottom to driving plates, so we will need three
particle tags: one for unbonded particles within the cell (tag=1), one for particles near
the bottom plate (tag=2) and one for particles near the top plate (tag=3). The following
code-fragment tags the particles accordingly and inserts them into the simulation:
#tag particles along base and top of rectangle
CHAPTER 3. ADVANCED MODELS 73
#then add the particles to the simulation object:
for pp in particleList:
centre = pp.getPosn()
radius = pp.getRadius()
Y = centre[1]
if (Y < 1.0): # particle is near the base (tag=2)
pp.setTag (2)
elif (Y > 9.0): # particle is near the top (tag=3)
pp.setTag (3)
else: # particle is inside the shear cell (tag=1)
pp.setTag (1)
sim.createParticle(pp) # add the particle to the simulation object
Having assigned a tag to each particle, the density of the particles can be set using
the following syntax:
#set the density of all particles:
sim.setParticleDensity (
tag = 1,
mask = -1,
Density = 100.0
)
Remember to also set the density of particles with tag=2 and tag=3 using similar sub-
routine calls.
In previous tutorials, the LinDampingPrms articial viscosity Interaction Group has
already been encountered. By assigning a relatively large value for the viscosity param-
eter, we can use LinDampingPrms to damp inertial eects. The following code-fragment
achieves this in our shear cell simulation:
#add local damping to avoid accumulating kinetic energy:
sim.createInteractionGroup (
LinDampingPrms (
name = "damping",
viscosity = 1.0,
maxIterations = 100
)
)
Due to the way ESyS-Particle implements damping, it is important that the speci-
cation 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 dene the boundary conditions. The next section describes how
to do this.
CHAPTER 3. ADVANCED MODELS 74
3.4.3 Servo walls and constant stress boundary conditions
As described in the introduction to this chapter, annular shear cells are loaded via a
combination of a constant vertical conning pressure applied to the top ring and shear of
the bottom ring at a constant rate. To simulate shear at a constant rate, we can simply
re-use the WallLoaderRunnable we encountered in Chapter 6. So as to match the typical
laboratory conditions, we will only shear the bottom wall. The following code-fragment
achieves that:
#import the WallLoaderRunnable (at the top of the script):
from WallLoader import WallLoaderRunnable
[...]
#add WallLoaderRunnables to shear the bottom driving plate:
wall_loader1 = WallLoaderRunnable(
LsmMpi = sim,
wallName = "bottom_wall",
vPlate = Vec3 (0.125, 0.0, 0.0),
startTime = 30000,
rampTime = 10000
)
sim.addPreTimeStepRunnable (wall_loader1)
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
sucient 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 inecient, requiring numerous communi-
cations 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 advan-
tageous 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:
#import the ServoWallLoaderRunnable (at the top of the script):
from ServoWallLoader import ServoWallLoaderRunnable
[...]
#add ServoWallLoaderRunnables to apply constant normal stress:
servo_loader1 = ServoWallLoaderRunnable(
LsmMpi = sim,
interactionName = "twall_bonds",
force = Vec3 (0.0, -1000.0, 0.0),
startTime = 0,
rampTime = 5000
)
sim.addPreTimeStepRunnable (servo_loader1)
The arguments to ServoWallLoaderRunnable instruct ESyS-Particle to linearly increase
the applied force on the top wall for 5000 timesteps, then maintain a constant vertical
force of 1000N thereafter. Recall that shear commences after 30000 timesteps, so there
is plenty of time for the model to equilibriate at the desired conning pressure prior to
shear commencing.
Now that the two boundary conditions are implemented, the physics of the shear cell
model are complete. Of course, it would be advantageous to include some FieldSavers
or a CheckPointer prior to commencing a simulation. The next section describes how
to use WallVectorFieldSavers to measure the bulk friction coecient of the sheared
unbonded particles, as well as how to observe whether dilation occurs during shear.
3.4.4 Computation of bulk frictional properties of granular me-
dia
The primary purpose of DEM shear cell simulations is to provide a means to calibrate
the microphysical model parameters so that the bulk frictional properties of the DEM
model match those measured in laboratory experiments. In the laboratory, the bulk
friction coecient of a sheared granular material is estimated by measuring the amount
of force (or torque) required to maintain a constant rate of shear at the boundaries. The
bulk friction coecient (
bulk
) is dened as the measured shear force (F
s
) divided by the
applied normal force (F
n
) i.e.

bulk

F
s
F
n
CHAPTER 3. ADVANCED MODELS 76
Figure 3.16: Time-series of the eective bulk friction coecient from a two-dimensional
annular shear cell simulation (from shearcell.py).
Typically a time-series of the bulk friction coecient is averaged in order to obtain a
measure of the mean bulk friction coecient. The standard deviation provides a measure
of the degree of variability of the bulk friction.
It should be obvious that in order to estimate the bulk friction coecient in the
DEM shear cell simulations, we need to extract time-series of the forces acting on the
walls throughout the simulations. The following WallVectorFieldSaver will provide the
necessary raw data:
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 coecient from a shear cell simulation is provided
in Figure 3.16. In this gure we average the normal and shear forces acting on the two
walls as measures for F
n
and F
s
in the formula above. Note that during the initial stage
of the simulation, the bulk friction coecient 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 coecient rapidly rises
to approximately
bulk
0.65. Thereafter, rough stable sliding of the granular material
ensues, with the bulk friction coecient remaining near-constant.
This result is typical when shearing a granular material comprised of irrotational
spheres with a constant internal friction coecient (here we set the internal friction co-
ecient as 0.6). Past research has demonstrated that inhibiting rolling of DEM particles
(through the use of NRotSpheres) results in a bulk friction coecient 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 coecient increases. However, when rotational, unbonded particles
are used, the bulk friction coecient is signicantly smaller, and almost independent of
the choice of internal friction coecient.
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) conrms 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 eective
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 nal 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
observed in laboratory experiments.
There are numerous other instances where increasing the geometrical realism of a DEM
model can yield more realistic results without resorting to more complicated interaction
laws. This observation is one of the reasons ESyS-Particle is designed for use on parallel
supercomputers. Increased geometrical realism typically requires the use of signicantly
larger numbers of particles. ESyS-Particle permits these signicantly larger models to
be executed on parallel supercomputers in almost the same amount of time as a smaller
model on a desktop PC. As simulation models become increasingly complex, one may
need to carefully design models to achieve optimal performance or make use of third
party tools to generate and analyse these models. The following chapter describes some
of the other resources available to assist advanced users of ESyS-Particle.
Of course, in order to construct these geometrically complex models, one requires a
exible tool for model construction. The next chapter introduces GenGeo, a companion
library to ESyS-Particle, designed as a tool for constructing DEM models that are much
more geometrically complex than we have encountered thus far.
Chapter 4
Jazzing things up: complicated
particle geometries using GenGeo
4.1 A short introduction to GenGeo
The GenGeo library is build around three basic concepts:
geometrical volumes to ll with particles
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 exible and to allow many dierent 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.
4.1.3 Neighbour Tables
- Verlet
- 2D/3D
- Circular Boundaries
4.2 Particles in a box: a simple GenGeo example
This section introduces the basic features of GenGeo using a very simple example: lling
a box with particles. The resulting particle packing is similar to that produces inside the
ESyS-particle simulation described in section 3.2.1. However, given that it is saved to a
le it can be re-used for multiple simulations. The full code for the example is available
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:
1. setting up a neighbour table
2. dening the volume to be lled - i.e. the box
3. setting up the packer
4. running the packer to ll the box and, if required, bond the particles together
5. write the particle data to an output le
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
from gengeo import *
which of course assumes that the GenGeo library
1
is in the python path. In order to
make the code more readable we rst dene a couple of parameters which will be needed
repeatedly throughout the script, namely the dimensions of the box we intent to ll 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
dene it fully
2
. So the next section of the script looks like this:
# box dimensions
xdim=10
ydim=20
zdim=10
# particle size range
minRadius = 0.2
maxRadius = 1.0
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 dene
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 dierence is to avoid
name conicts when using both GenGeo and ESyS-Particle libraries in the same Python
script. So the denition 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 dened 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 dene both the volume to be lled 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 dened 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 dierent
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 param-
eters 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 t-
ting procedure (maxIterations) and the precision of the sphere tting, 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 ne 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 dierent 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 dened the actual work
can start. First the packer needs to ll the volume with particles. In the simplest case
this requires two arguments to the generatePacking function of the packer: the volume
to ll and the neighbour table in which to store the particles. Therefore the function call
to ll the box volume with particles is
# pack particles into volume
packer.generatePacking(
volume=box,
ntable=mntable
)
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 infor-
mations 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:
# create bonds between neighbouring particles:
mntable.generateBonds(
groupID=0,
tolerance=1.0e-5,
bondID=0
)
And after this the model is fully built. The only thing left to do is to write the model
data into a le 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 le in dierent ways which are controlled by the outputStyle argument.
If outputStyle=1 a geo le is written which can be used in ESyS-Particle scripts, if
outputStyle=2 a VTK le
4
is written for the use with visualisation software such as
Paraview(tm). Of course the write function also needs the le name as rst argument,
so to write both a geo and a VTK le the calls should be:
# write a geometry file
4
Specically a VTK-XML unstructured grid le - see the documentation of VTK toolkit on
www.vtk.org for more details on the le format.
CHAPTER 4. GENGEO 83
Figure 4.1: An image (generated with Paraview) of the box lled 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 lled 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 nishes it should have produces two les, 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
4.3 Particles in a box Mk II: making the the bound-
aries smooth(er)
The reason why the walls of the box generated in the previous section are not as smooth
as expected is that the InsertGenerator uses the box volume only determine if a particle
is inside the volume or not, it does not t particles to the faces of the box. This can be
changed by adding some Plane objects to the box volume. To do this, it is necessary
to dene the planes rst. Each Plane object is dened by a point on the plane and the
normal direction of the plane. To add a Plane object for each face of the box a total of
6 planes are needed, three passing through the point (0,0,0), which is called maxPoint
in the script described in the previous section, with normals pointing in the positive x-,
y- and z-direction and three passing through the opposite corner of the box, which was
called maxPoint in the script. So the denition of a plane parallel to the xy-plane, i.e.
with a normal pointing in the z-direction, and going through the point minPoint is
bottomPlane=Plane(
origin=minPoint,
normal=Vector3(0.0,0.0,1.0)
)
The other 5 planes can be dened in the same way or, more compact, by the following
lines:
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))
To enable the packer to t the particles to the planes when lling the box volume the
Plane objects need to be added to the BoxWithPlanes3D object by using its addPlane
member function. This function takes a Plane object as its only argument. To add the
bottomPlane dened above to the box volume the call is
box.addPlane(
Plane=bottomPlane
)
and for the other ve planes, in the compact form,
box.addPlane(leftPlane)
box.addPlane(frontPlane)
box.addPlane(topPlane)
box.addPlane(rightPlane)
box.addPlane(backPlane)
If these lines are added 4.2 somewhere between the denition of the box volume and the
call to generate the particle packing in the simple script lling a box which was developed
in the previous section we have the complete script to generate a box full of particles
where the sides are as smooth as possible for the given particle size range. The full code
for the example is available in Appendix A.2.2. The resulting model can be seen in Figure
4.2 A). If the surfaces are still too rough for a particular application the smoothness can
be improved by extending the range of particle sizes towards smaller radii. This can
easily be achieved in the script described here by changing the variable minRadius to an
appropriate value. The result of setting minRadius=0.05 is shown in Figure 4.2 B).
CHAPTER 4. GENGEO 85
Figure 4.2: Images (generated with Paraview) of a box with particles tted 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.
4.3.1 Smoothness, particle sizes and particle numbers
However, while smooth surfaces and dense particle arrangements can be realized with
packings using wide size distribution of particles, i.e. a large ratio r
max
: r
min
it should
also be noted that this leads to a signicant increase in the number of particles for the
same model size. A very rough estimate would be that for a given maximum particle size
the total number of particles in a volume goes up approximately as n
p
1/r
min
2 6
where
n
p
is the total number of particles. For the models shown in Figure 4.2 the total number
of particles went up from about 5300 to 72000, representing an increase by a factor of
13.6 where the rule mentioned above would predict a factor of 16 for the reduction of the
minimum particle radius from 0.2 to 0.05.
A second issue which needs to be taken care of when using large particle size ranges
is that in the denition of the InsertGenerator3D the maximum number of attempts to
insert a particle should be increased. The rationale behind this is that the packer now
needs to nd smaller holes in which to insert particles so it needs to try more often.
The model shown in Figure 4.2 B) was generated using insertFails = 10000. There is
unfortunately no hard rule how many attempts to insert a particle should be used for a
specic combination of volume and particle size range. A possible way to nd a suitable
number of insertion attempts for a specic problem is to re-run the packing script a couple
of times with dierent values insertFails and observe how the density of the packing
changes. A good way to measure the packing density is to calculate the porosity of the
model. This can be done by calculating the combined volume of all particles using the
getSumVolume member function of the neighbour table and subtracting this from the total
6
The exact relationship between model volume, particle size range and particle number is more com-
plicated, particularly for relatively narrow particle size ranges. However, the 1/r
min
2
relationship is a
usable rule of thumb. N.B. This applies for xed r
max
- for xed r
max
/r
min
the relation is obviously
n
p
1/r
3
CHAPTER 4. GENGEO 86
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
inuence 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 dierent realizations of the model.
4.4 Getting serious: groups, particle tags and bond
tags
4.5 Grouping particles: a box of clusters
For some types of simulations it can be useful if the particles in the model are grouped into
grains or clusters where the intra-cluster bonds do have dierent properties than the
bonds between the clusters
7
. The approach for generating such a particle arrangement
described in this section has for example been used by Abe and Mair, GRL 2009 [?].
This example also demonstrates how to use a number of particle groups in the neighbour
table is greater than 1. To simplify the script here only a single box is lled with particle
clusters. The full code for the example is available in Appendix A.2.3.
The key ideas of the particle clustering approach described here are:
1. The clustering happens after the particle packing.
8
2. There are a number of seeds distributed within the volume
3. Each particle is tagged depending on which of the seeds is closest to it
4. When generating the bonds, bonds between particles with dierent tags get a dif-
ferent tag than bonds between particles with the same tag
9
Point 1. above means that the box lling 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 dierent clusters.
8
Approaches dening 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 t 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 specic 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 dierent 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.
# construct & insert seed "pseudo-particle"
cseed=Sphere(seed_pos,0.0)
tag=i*ncluster_y*ncluster_z+j*ncluster_z+k
cseed.setTag(tag)
mntable.insert(cseed,1)
10
The other real particles are in group 0 - the generatePacking call places them there by default.
CHAPTER 4. GENGEO 88
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: rst 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 dierent 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
# tag particle according to nearest seed
mntable.tagParticlesToClosest(
groupID1=0,
groupID2=1
)
# 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 le 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 ll 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 tting 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.6 Lateral thinking: hierarchical packing for com-
plex models
4.7 If nothing else helps - the MeshVolume
For complicated geometries which can not be reasonably expressed as combination of
simple volumes there is a way to ll any volume bounded by a closed, triangulated surface
with particles. However, it should be noted that this can be computationally rather
expensive, in particular that the computation time goes up roughly linear with the number
of triangles forming the surface. On the other hand, it is usually faster than constructing
the same volume from a set of convex polyhedra. Also, it should be noted that there is
currently no facility to import any given mesh format into GenGeo.
4.8 What else is there?
This section provides short descriptions of the remaining features of the GenGeo library.
Features marked EXPERIMENTAL may not be completely implemented and / or tested
yet and therefore have bugs and limitations.
CHAPTER 4. GENGEO 90
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
4.9 Additional ESyS-Particle resources and documen-
tation
Appendix A
Code-listings for tutorial examples
A.1 ESyS-Particle scripts
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
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
#specify the number of timesteps and timestep increment:
sim.setNumTimeSteps(10000)
sim.setTimeStepSize(0.001)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#add the first particle to the domain:
particle=NRotSphere(id=0, posn=Vec3(-5,5,-5), radius=1.0, mass=1.0)
particle.setLinearVelocity(Vec3(1.0,-1.0,1.0))
sim.createParticle(particle)
#add the second particle to the domain:
particle=NRotSphere(id=1, posn=Vec3(5,5,5), radius=1.5, mass=2.0)
particle.setLinearVelocity(Vec3(-1.0,-1.0,-1.0))
sim.createParticle(particle)
#specify the type of interactions between colliding particles:
sim.createInteractionGroup(
NRotElasticPrms(
name = "elastic_repulsion",
normalK = 10000.0,
scaling = True
)
)
#Execute the simulation:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 94
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 95
A.1.2 bingle output.py
#bingle_output.py: A two-particle collision simulation
# with ASCII data output
# Author: D. Weatherley
# Date: 15 May 2007
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2007.
#
#
#provide forward compatibility for Python 2 interpreters
from __future__ import print_function
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import Vec3, BoundingBox
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
#set the number of timesteps and timestep increment:
sim.setNumTimeSteps(10000)
sim.setTimeStepSize(0.001)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#add the first particle to the domain:
particle=NRotSphere(id=0, posn=Vec3(-5,5,-5), radius=1.0, mass=1.0)
particle.setLinearVelocity(Vec3(1.0,-1.0,1.0))
sim.createParticle(particle)
#add the second particle to the domain:
particle=NRotSphere(id=1, posn=Vec3(5,5,5), radius=1.5, mass=2.0)
particle.setLinearVelocity(Vec3(-1.0,-1.0,-1.0))
sim.createParticle(particle)
#specify the type of interactions between colliding particles:
sim.createInteractionGroup(
NRotElasticPrms(
name = "elastic_repulsion",
normalK = 10000.0,
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 96
scaling = True
)
)
#compute the specified number of timesteps:
N_max = sim.getNumTimeSteps()
n=0
while (n < N_max):
#compute a single timestep:
sim.runTimeStep()
# print the particle position to stdout every 100 timesteps:
if (n%100==0):
particles = sim.getParticleList()
p1 = particles[0].getPosn()
p2 = particles[1].getPosn()
print(n,p1,p2)
# update the total number of timesteps computed (n):
n += 1
#Exit the simulation.
sim.exit()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 97
A.1.3 bingle chk.py
#bingle_chk.py: A simple two-particle collision simulation
# with data output via a CheckPointer
# 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
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
#specify the number of timesteps and timestep increment
sim.setNumTimeSteps(10000)
sim.setTimeStepSize(0.001)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#add the first particle to the domain:
particle=NRotSphere(id=0, posn=Vec3(-5,5,-5), radius=1.0, mass=1.0)
particle.setLinearVelocity(Vec3(1.0,-1.0,1.0))
sim.createParticle(particle)
#add the second particle to the domain:
particle=NRotSphere(id=1, posn=Vec3(5,5,5), radius=1.5, mass=2.0)
particle.setLinearVelocity(Vec3(-1.0,-1.0,-1.0))
sim.createParticle(particle)
#specify the type of interactions between colliding particles:
sim.createInteractionGroup(
NRotElasticPrms(
name = "elastic_repulsion",
normalK = 10000.0,
scaling = True
)
)
#add a CheckPointer to save simulation data at regular intervals:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 98
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "bingle_data",
beginTimeStep = 0,
endTimeStep = 10000,
timeStepIncr = 100
)
)
#Execute the simulation
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 99
A.1.4 bingle vis.py
#bingle_vis.py: A two-particle collision simulation
# with visualisation using povray
# 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 esys.lsm.vis import povray
def snapshot(particles=None, index=0):
pkg = povray
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_{0:04d}.png".format(index),
size=[800,600]
)
return
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
#set the number of timesteps and timestep increment:
sim.setNumTimeSteps(10000)
sim.setTimeStepSize(0.001)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 100
sim.setSpatialDomain(domain)
#add the first particle to the domain:
particle=NRotSphere(id=0, posn=Vec3(-5,5,-5), radius=1.0, mass=1.0)
particle.setLinearVelocity(Vec3(1.0,-1.0,1.0))
sim.createParticle(particle)
#add the second particle to the domain:
particle=NRotSphere(id=1, posn=Vec3(5,5,5), radius=1.5, mass=2.0)
particle.setLinearVelocity(Vec3(-1.0,-1.0,-1.0))
sim.createParticle(particle)
#specify the type of interactions between colliding particles:
sim.createInteractionGroup(
NRotElasticPrms(
name = "elastic_repulsion",
normalK = 10000.0,
scaling = True
)
)
#compute the specified number of timesteps:
N_max = sim.getNumTimeSteps()
n=0
while (n < N_max):
#compute a single timestep:
sim.runTimeStep()
# Take a snapshot of the simulation every 100 timesteps:
if (n%100==0):
particles = sim.getParticleList()
snapshot(particles=particles, index=n)
# update the total number of timesteps computed (n):
n += 1
#Exit the simulation.
sim.exit()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 101
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.
from esys.lsm import *
from esys.lsm.util import Vec3, BoundingBox
from esys.lsm.vis import povray
class POVsnaps (Runnable):
def __init__(self, sim, interval):
Runnable.__init__(self)
self.sim = sim
self.interval = interval
self.count = 0
self.configure()
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.6 bingle Runnable.py
#bingle_Runnable.py: A two-particle collision simulation
# with visualisation via a Runnable
# Author: D. Weatherley
# Date: 17 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
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
#set the number of timesteps and timestep increment:
sim.setNumTimeSteps(10000)
sim.setTimeStepSize(0.001)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#add the first particle to the domain:
particle=NRotSphere(id=0, posn=Vec3(-5,5,-5), radius=1.0, mass=1.0)
particle.setLinearVelocity(Vec3(1.0,-1.0,1.0))
sim.createParticle(particle)
#add the second particle to the domain:
particle=NRotSphere(id=1, posn=Vec3(5,5,5), radius=1.5, mass=2.0)
particle.setLinearVelocity(Vec3(-1.0,-1.0,-1.0))
sim.createParticle(particle)
#specify the type of interactions between colliding particles:
sim.createInteractionGroup(
NRotElasticPrms(
name = "elastic_repulsion",
normalK = 10000.0,
scaling = True
)
)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 104
#add a POVsnaps Runnable for taking images
#of the particles every 100 timesteps:
povcam = POVsnaps(sim=sim, interval=100)
sim.addPostTimeStepRunnable(povcam)
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 105
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
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
#set the number of timesteps and timestep increment:
sim.setNumTimeSteps(20000)
sim.setTimeStepSize(0.001)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#add a particle to the domain:
particle=NRotSphere(id=0, posn=Vec3(0,5,0), radius=1.75, mass=1.8)
particle.setLinearVelocity(Vec3(1.0,10.0,1.0))
sim.createParticle(particle)
#initialise gravity in the domain:
sim.createInteractionGroup(
GravityPrms(name="earth-gravity", acceleration=Vec3(0,-9.81,0))
)
#add a horizontal wall to act as a floor on which to bounce particles:
sim.createWall(
name="floor",
posn=Vec3(0,-10,0),
normal=Vec3(0,1,0)
)
#specify the type of interactions between wall and particles:
sim.createInteractionGroup(
NRotElasticWallPrms(
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 106
name = "elasticWall",
wallName = "floor",
normalK = 10000.0
)
)
#add local viscosity to simulate air resistance:
sim.createInteractionGroup(
LinDampingPrms(
name="linDamping",
viscosity=0.1,
maxIterations=100
)
)
#add a POVsnaps Runnable:
povcam = POVsnaps(sim=sim, interval=100)
povcam.configure()
sim.addPostTimeStepRunnable(povcam)
#execute the simulation
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 107
A.1.8 gravity cube.py
#gravity_cube.py: A bouncing cube simulation using ESyS-Particle
# Author: D. Weatherley
# Date: 15 May 2007
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2007.
#
#
#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 Vec3, BoundingBox
from esys.lsm.geometry import CubicBlock,ConnectionFinder
from POVsnaps import POVsnaps
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi(numWorkerProcesses=1, mpiDimList=[1,1,1])
sim.initNeighbourSearch(
particleType="NRotSphere",
gridSpacing=2.5,
verletDist=0.5
)
#set the number of timesteps and timestep increment:
sim.setNumTimeSteps(10000)
sim.setTimeStepSize(0.001)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#add a cube of particles to the domain:
cube = CubicBlock(dimCount=[6,6,6], radius=0.5)
cube.rotate(axis=Vec3(0,0,3.141592654/6.0),axisPt=Vec3(0,0,0))
sim.createParticles(cube)
#create bonds between particles separated by less than the specified
#maxDist:
sim.createConnections(
ConnectionFinder(
maxDist = 0.005,
bondTag = 1,
pList = cube
)
)
#specify bonded elastic interactions between bonded particles:
bondGrp = sim.createInteractionGroup(
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 108
NRotBondPrms(
name = "sphereBonds",
normalK = 10000.0,
breakDistance = 50.0,
tag = 1,
scaling = True
)
)
#initialise gravity in the domain:
sim.createInteractionGroup(
GravityPrms(name="earth-gravity", acceleration=Vec3(0,-9.81,0))
)
#add a horizontal wall to act as a floor to bounce particle off:
sim.createWall(
name="floor",
posn=Vec3(0,-10,0),
normal=Vec3(0,1,0)
)
#specify the type of interactions between wall and particles:
sim.createInteractionGroup(
NRotElasticWallPrms(
name = "elasticWall",
wallName = "floor",
normalK = 10000.0
)
)
#add local viscosity to simulate air resistance:
sim.createInteractionGroup(
LinDampingPrms(
name="linDamping",
viscosity=0.1,
maxIterations=100
)
)
#add a POVsnaps Runnable:
povcam = POVsnaps(sim=sim, interval=100)
povcam.configure(lookAt=Vec3(0,0,0), camPosn=Vec3(14,0,14))
sim.addPostTimeStepRunnable(povcam)
#execute the simulation
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 109
A.1.9 slope fail.py
#slope_fail.py: A slope failure simulation using ESyS-Particle
# Author: D. Weatherley
# Date: 23 December 2008
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2008.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi (numWorkerProcesses = 1, mpiDimList = [1,1,1])
sim.initNeighbourSearch (
particleType = "NRotSphere",
gridSpacing = 2.5000,
verletDist = 0.1000
)
#specify the number of timesteps and the timestep increment:
sim.setNumTimeSteps (50000)
sim.setTimeStepSize (1.0000e-04)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#construct a block of particles with radii in range [0.2,0.5]:
geoRandomBlock = RandomBoxPacker (
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()
#add the particle assembly to the simulation object:
sim.createParticles(geoRandomBlock_particles)
#add a wall as a floor for the model:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 110
sim.createWall (
name = "floor",
posn = Vec3(0.0000, 0.0000, 0.0000),
normal = Vec3(0.0000, 1.0000, 0.0000)
)
#specify that particles undergo unbonded elastic repulsion:
sim.createInteractionGroup (
NRotElasticPrms (
name = "repulsion",
normalK = 1.0000e+03,
scaling = True
)
)
#specify that particles undergo elastic repulsion from the floor:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "wall_repell",
wallName = "floor",
normalK = 1.0000e+04
)
)
#specify the direction and magnitude of gravity:
sim.createInteractionGroup (
GravityPrms (
name = "gravity",
acceleration = Vec3(0.0000, -9.8100, 0.0000)
)
)
#add viscosity to damp particle oscillations:
sim.createInteractionGroup (
LinDampingPrms (
name = "viscosity",
viscosity = 0.1000,
maxIterations = 100
)
)
#add a CheckPointer to store simulation data:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "slope_data",
beginTimeStep = 0,
endTimeStep = 50000,
timeStepIncr = 1000
)
)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 111
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 112
A.1.10 slope friction.py
#slope_friction.py: A slope failure simulation with friction using
# ESyS-Particle
# Author: D. Weatherley
# Date: 23 December 2008
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2008.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi (numWorkerProcesses = 1, mpiDimList = [1,1,1])
sim.initNeighbourSearch (
particleType = "NRotSphere",
gridSpacing = 2.5000,
verletDist = 0.1000
)
#specify the number of timesteps and the timestep increment:
sim.setNumTimeSteps (50000)
sim.setTimeStepSize (1.0000e-04)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#construct a block of particles with radii in range [0.2,0.5]:
geoRandomBlock = RandomBoxPacker (
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()
#add the particle assembly to the simulation object:
sim.createParticles(geoRandomBlock_particles)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 113
#add a wall as a floor for the model:
sim.createWall (
name = "floor",
posn = Vec3(0.0000, 0.0000, 0.0000),
normal = Vec3(0.0000, 1.0000, 0.0000)
)
#specify that particles undergo frictional interactions:
sim.createInteractionGroup (
NRotFrictionPrms (
name = "friction",
normalK = 1000.0,
dynamicMu = 0.6,
shearK = 100.0,
scaling = True
)
)
#specify that particles undergo elastic repulsion from the floor:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "wall_repell",
wallName = "floor",
normalK = 1.0000e+04
)
)
#specify the direction and magnitude of gravity:
sim.createInteractionGroup (
GravityPrms (
name = "gravity",
acceleration = Vec3(0.0000, -9.8100, 0.0000)
)
)
#add viscosity to damp particle oscillations:
sim.createInteractionGroup (
LinDampingPrms (
name = "viscosity",
viscosity = 0.1000,
maxIterations = 100
)
)
#add a CheckPointer to store simulation data:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "slope_data",
beginTimeStep = 0,
endTimeStep = 50000,
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 114
timeStepIncr = 1000
)
)
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 115
A.1.11 slope friction floor.py
#slope_friction_floor.py: A slope failure simulation with friction
# and a bumpy floor using ESyS-Particle
# Author: D. Weatherley
# Date: 23 December 2008
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2008.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
#instantiate a simulation object
#and initialise the neighbour search algorithm:
sim = LsmMpi (numWorkerProcesses = 1, mpiDimList = [1,1,1])
sim.initNeighbourSearch (
particleType = "NRotSphere",
gridSpacing = 2.5000,
verletDist = 0.1000
)
#specify the number of timesteps and the timestep increment:
sim.setNumTimeSteps (100000)
sim.setTimeStepSize (1.0000e-04)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#construct a block of particles with radii in range [0.2,0.5]:
geoRandomBlock = RandomBoxPacker (
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()
#add particles to simulation one at a time,
#tagging those nearest the floor:
for pp in geoRandomBlock_particles:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 116
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
#add a wall as a floor for the model:
sim.createWall (
name = "floor",
posn = Vec3(0.0000, 0.0000, 0.0000),
normal = Vec3(0.0000, 1.0000, 0.0000)
)
#specify that particles undergo frictional interactions:
sim.createInteractionGroup (
NRotFrictionPrms (
name = "friction",
normalK = 1000.0,
dynamicMu = 0.6,
shearK = 100.0,
scaling = True
)
)
#specify that tagged particles undergo
#bonded elastic interactions with floor:
sim.createInteractionGroup (
NRotBondedWallPrms (
name = "floor_bonds",
wallName = "floor",
normalK = 10000.0,
particleTag = 12321
)
)
#specify the direction and magnitude of gravity:
sim.createInteractionGroup (
GravityPrms (
name = "gravity",
acceleration = Vec3(0.0000, -9.8100, 0.0000)
)
)
#add viscosity to damp particle oscillations:
sim.createInteractionGroup (
LinDampingPrms (
name = "viscosity",
viscosity = 0.1000,
maxIterations = 100
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 117
)
)
#add a CheckPointer to store simulation data:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "slope_data",
beginTimeStep = 0,
endTimeStep = 100000,
timeStepIncr = 1000
)
)
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 118
A.1.12 slope friction walls.py
#slope_friction_walls.py: A slope failure simulation using quarter
# symmetry and a bumpy floor using ESyS-Particle
# Author: D. Weatherley
# Date: 23 December 2008
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2008.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
#instantiate a simulation object and
#initialise the neighbour search algorithm:
sim = LsmMpi (numWorkerProcesses = 1, mpiDimList = [1,1,1])
sim.initNeighbourSearch (
particleType = "NRotSphere",
gridSpacing = 2.5000,
verletDist = 0.1000
)
#specify the number of timesteps and the timestep increment:
sim.setNumTimeSteps (100000)
sim.setTimeStepSize (1.0000e-04)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#construct a block of particles with radii in range [0.2,0.5]:
geoRandomBlock = RandomBoxPacker (
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()
#add particles to simulation one at a time,
#tagging those nearest the floor
for pp in geoRandomBlock_particles:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 119
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
#add a wall as a floor for the model:
sim.createWall (
name = "floor",
posn = Vec3(0.0000, 0.0000, 0.0000),
normal = Vec3(0.0000, 1.0000, 0.0000)
)
#add a left side wall to the model:
sim.createWall (
name = "left_wall",
posn = Vec3(-5.0000, 0.0000, 0.0000),
normal = Vec3(1.0000, 0.0000, 0.0000)
)
#add a back wall to the model:
sim.createWall (
name = "back_wall",
posn = Vec3(0.0000, 0.0000, -5.0000),
normal = Vec3(0.0000, 0.0000, 1.0000)
)
#specify that particles undergo frictional interactions:
sim.createInteractionGroup (
NRotFrictionPrms (
name = "friction",
normalK = 1000.0,
dynamicMu = 0.6,
shearK = 100.0,
scaling = True
)
)
#specify that particles with tag 12321 undergo
#bonded elastic interactions with floor:
sim.createInteractionGroup (
NRotBondedWallPrms (
name = "floor_bonds",
wallName = "floor",
normalK = 10000.0,
particleTag = 12321
)
)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 120
#specify that particles undergo elastic repulsion
#from the left side wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "lw_repel",
wallName = "left_wall",
normalK = 1.0000e+04
)
)
#specify that particles undergo elastic repulsion
#from the back wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "bw_repel",
wallName = "back_wall",
normalK = 1.0000e+04
)
)
#specify the direction and magnitude of gravity:
sim.createInteractionGroup (
GravityPrms (
name = "gravity",
acceleration = Vec3(0.0000, -9.8100, 0.0000)
)
)
#add viscosity to damp particle oscillations:
sim.createInteractionGroup (
LinDampingPrms (
name = "viscosity",
viscosity = 0.1000,
maxIterations = 100
)
)
#add a CheckPointer to store simulation data:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "slope_data",
beginTimeStep = 0,
endTimeStep = 100000,
timeStepIncr = 1000
)
)
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 121
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
A.1.14 hopper flow.py
#hopper_flow.py: A hopper flow simulation with mesh walls using
# ESyS-Particle
# Author: W. Hancock
# Date: 10 July 2009
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2009.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
#instantiate a simulation object and
#initialise the neighbour search algorithm:
sim = LsmMpi (numWorkerProcesses = 1, mpiDimList = [1,1,1])
sim.initNeighbourSearch (
particleType = "NRotSphere",
gridSpacing = 2.5000,
verletDist = 0.1000
)
#specify the number of timesteps and the timestep increment:
sim.setNumTimeSteps (100000)
sim.setTimeStepSize (1.0000e-04)
#specify the spatial domain for the simulation:
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain(domain)
#construct a block of particles with radii in range [0.2,0.5]:
geoRandomBlock = RandomBoxPacker (
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()
#add particles to simulation one at a time,
#tagging those in layers of 2m
for pp in geoRandomBlock_particles:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 123
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
#add a wall as a floor for the model:
sim.readMesh(
fileName = "floorMesh.msh",
meshName = "floor_mesh_wall"
)
#add a left side wall to the model:
sim.createWall (
name = "left_wall",
posn = Vec3(-5.0000, 0.0000, 0.0000),
normal = Vec3(1.0000, 0.0000, 0.0000)
)
#add a right side wall to the model:
sim.createWall (
name = "right_wall",
posn = Vec3(5.0000, 0.0000, 0.0000),
normal = Vec3(-1.0000, 0.0000, 0.0000)
)
#add a back wall to the model:
sim.createWall (
name = "back_wall",
posn = Vec3(0.0000, 0.0000, -5.0000),
normal = Vec3(0.0000, 0.0000, 1.0000)
)
#add a front wall to the model:
sim.createWall (
name = "front_wall",
posn = Vec3(0.0000, 0.0000, 5.0000),
normal = Vec3(0.0000, 0.0000, -1.0000)
)
#specify that particles undergo frictional interactions:
sim.createInteractionGroup (
NRotFrictionPrms (
name = "friction",
normalK = 1000.0,
dynamicMu = 0.6,
shearK = 100.0,
scaling = True
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 124
)
)
#specify that particles undergo elastic repulsion
#with the floor mesh wall:
sim.createInteractionGroup (
NRotElasticTriMeshPrms (
name = "floorWall_repell",
meshName = "floor_mesh_wall",
normalK = 1.0000e+04
)
)
#specify that particles undergo elastic repulsion
#from the left side wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "lw_repell",
wallName = "left_wall",
normalK = 1.0000e+04
)
)
#specify that particles undergo elastic repulsion
#from the right side wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "rw_repell",
wallName = "right_wall",
normalK = 1.0000e+04
)
)
#specify that particles undergo elastic repulsion
#from the back wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "bw_repell",
wallName = "back_wall",
normalK = 1.0000e+04
)
)
#specify that particles undergo elastic repulsion
#from the front wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "fw_repell",
wallName = "front_wall",
normalK = 1.0000e+04
)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 125
)
#specify the direction and magnitude of gravity:
sim.createInteractionGroup (
GravityPrms (
name = "gravity",
acceleration = Vec3(0.0000, -9.8100, 0.0000)
)
)
#add viscosity to damp particle oscillations:
sim.createInteractionGroup (
LinDampingPrms (
name = "viscosity",
viscosity = 0.1000,
maxIterations = 100
)
)
#add a CheckPointer to store simulation data:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "flow_data",
beginTimeStep = 0,
endTimeStep = 100000,
timeStepIncr = 1000
)
)
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 126
A.1.15 rot compress.py
#rot_compress.py: A uniaxial compression simulation using ESyS-Particle
# Author: D. Weatherley
# Date: 27 December 2008
# Organisation: ESSCC, University of Queensland
# (C) All rights reserved, 2008.
#
#
#import the appropriate ESyS-Particle modules:
from esys.lsm import *
from esys.lsm.util import *
from esys.lsm.geometry import *
from WallLoader import WallLoaderRunnable
#instantiate a simulation object:
sim = LsmMpi (numWorkerProcesses = 1, mpiDimList = [1,1,1])
#initialise the neighbour search algorithm:
sim.initNeighbourSearch (
particleType = "RotSphere",
gridSpacing = 5.0000,
verletDist = 0.08000
)
#set the number of timesteps and timestep increment:
sim.setNumTimeSteps (250000)
sim.setTimeStepSize (1.0000e-06)
#specify the spatial domain for the simulation
domain = BoundingBox(Vec3(-20,-20,-20), Vec3(20,20,20))
sim.setSpatialDomain (domain)
#create a prism of spherical particles:
geoRandomBlock = RandomBoxPacker (
minRadius = 0.400,
maxRadius = 2.0000,
cubicPackRadius = 2.2000,
maxInsertFails = 5000,
bBox = BoundingBox(
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()
#add the particles to the simulation object:
sim.createParticles(geoRandomBlock_particles)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 127
#bond particles together with bondTag = 1:
sim.createConnections(
ConnectionFinder(
maxDist = 0.005,
bondTag = 1,
pList = geoRandomBlock_particles
)
)
#create a wall at the bottom of the model:
sim.createWall (
name = "bottom_wall",
posn = Vec3(0.0000, 0.0000, 0.0000),
normal = Vec3(0.0000, 1.0000, 0.0000)
)
#create a wall at the top of the model:
sim.createWall (
name = "top_wall",
posn = Vec3(0.0000, 20.0000, 0.0000),
normal = Vec3(0.0000, -1.0000, 0.0000)
)
#create rotational elastic-brittle bonds between particles:
pp_bonds = sim.createInteractionGroup (
BrittleBeamPrms(
name="pp_bonds",
youngsModulus=100000.0,
poissonsRatio=0.25,
cohesion=100.0,
tanAngle=1.0,
tag=1
)
)
#initialise frictional interactions for unbonded particles:
sim.createInteractionGroup (
FrictionPrms(
name="friction",
youngsModulus=100000.0,
poissonsRatio=0.25,
dynamicMu=0.4,
staticMu=0.6
)
)
#create an exclusion between bonded and frictional interactions:
sim.createExclusion (
interactionName1 = "pp_bonds",
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 128
interactionName2 = "friction"
)
#specify elastic repulsion from the bottom wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "bottom_wall_repel",
wallName = "bottom_wall",
normalK = 100000.0
)
)
#specify elastic repulsion from the top wall:
sim.createInteractionGroup (
NRotElasticWallPrms (
name = "top_wall_repel",
wallName = "top_wall",
normalK = 100000.0
)
)
#add translational viscous damping:
sim.createInteractionGroup (
LinDampingPrms(
name="damping1",
viscosity=0.002,
maxIterations=50
)
)
#add rotational viscous damping:
sim.createInteractionGroup (
RotDampingPrms(
name="damping2",
viscosity=0.002,
maxIterations=50
)
)
#add a wall loader to move the top wall:
wall_loader1 = WallLoaderRunnable(
LsmMpi = sim,
wallName = "top_wall",
vPlate = Vec3 (0.0, -0.125, 0.0),
startTime = 0,
rampTime = 50000
)
sim.addPreTimeStepRunnable (wall_loader1)
#add a wall loader to move the bottom wall:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 129
wall_loader2 = WallLoaderRunnable(
LsmMpi = sim,
wallName = "bottom_wall",
vPlate = Vec3 (0.0, 0.125, 0.0),
startTime = 0,
rampTime = 50000
)
sim.addPreTimeStepRunnable (wall_loader2)
#create a FieldSaver to store number of bonds:
sim.createFieldSaver (
InteractionScalarFieldSaverPrms(
interactionName="pp_bonds",
fieldName="count",
fileName="nbonds.dat",
fileFormat="SUM",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=1
)
)
#create a FieldSaver to store the total kinetic energy of the particles:
sim.createFieldSaver (
ParticleScalarFieldSaverPrms(
fieldName="e_kin",
fileName="ekin.dat",
fileFormat="SUM",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=1
)
)
#create a FieldSaver to store potential energy stored in bonds:
sim.createFieldSaver (
InteractionScalarFieldSaverPrms(
interactionName="pp_bonds",
fieldName="potential_energy",
fileName="epot.dat",
fileFormat="SUM",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=1
)
)
#create a FieldSaver to wall positions:
posn_saver = WallVectorFieldSaverPrms(
wallName=["bottom_wall", "top_wall"],
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 130
fieldName="Position",
fileName="out_Position.dat",
fileFormat="RAW_SERIES",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=10
)
sim.createFieldSaver(posn_saver)
#create a FieldSaver to wall forces:
force_saver = WallVectorFieldSaverPrms(
wallName=["bottom_wall", "top_wall"],
fieldName="Force",
fileName="out_Force.dat",
fileFormat="RAW_SERIES",
beginTimeStep=0,
endTimeStep=250000,
timeStepIncr=10
)
sim.createFieldSaver(force_saver)
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 131
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 *
#This script implements a Runnable designed to move a wall at a specified
#speed. The Runnable also implements initial acceleration of the wall
#from zero to the desired speed as well as an optional initial idle
#period during which the wall does not move.
class WallLoaderRunnable (Runnable):
def __init__ (self,
LsmMpi=None,
wallName=None,
vPlate=Vec3(0,0,0),
startTime=0,
rampTime = 200):
"""
Subroutine to initialise the Runnable and store parameter values.
"""
Runnable.__init__(self)
self.sim = LsmMpi
self.wallName = wallName
self.Vplate = vPlate
self.dt = self.sim.getTimeStepSize()
self.rampTime = rampTime
self.startTime = startTime
self.Nt = 0
def run (self):
"""
Subroutine to move the specified wall. After self.startTime
timesteps, the speed of the wall increases linearly over
self.rampTime timesteps until the desired wall speed is achieved.
Thereafter the wall is moved at that speed.
"""
if (self.Nt >= self.startTime):
#compute the slowdown factor if still accelerating the wall:
if (self.Nt < (self.startTime + self.rampTime)):
f = float(self.Nt - self.startTime) / float(self.rampTime)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 132
else:
f = 1.0
#compute the amount by which to move the wall this timestep:
Dplate = Vec3(
f*self.Vplate[0]*self.dt,
f*self.Vplate[1]*self.dt,
f*self.Vplate[2]*self.dt
)
#instruct the simulation to move the wall:
self.sim.moveWallBy (self.wallName, Dplate)
#count the number of timesteps completed thus far:
self.Nt += 1
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 133
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
#create a simulation container object:
# N.B. there must be at least two sub-divisions
# in the X-direction for periodic boundaries
sim = LsmMpi (numWorkerProcesses=2, mpiDimList=[2,1,1])
sim.initNeighbourSearch (
particleType = "NRotSphere",
gridSpacing = 2.5,
verletDist = 0.5
)
#specify the number of timesteps and timestep increment:
sim.setNumTimeSteps(100000)
sim.setTimeStepSize(0.001)
#enforce two-dimensional computations:
sim.force2dComputations (True)
#specify the spatial domain and direction of periodic boundaries:
domain = BoundingBox ( Vec3 (0,0,0), Vec3 (10,10,0) )
sim.setSpatialDomain (
bBox = domain,
circDimList = [True, False, False]
)
#construct a rectangle of unbonded particles:
packer = RandomBoxPacker (
minRadius = 0.1,
maxRadius = 0.5,
cubicPackRadius = 2.2,
maxInsertFails = 1000,
bBox = BoundingBox(
Vec3(0.0, 0.0,0.0),
Vec3(10.0, 10.0, 0.0)
),
circDimList = [True, False, False],
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 134
tolerance = 1.0e-5
)
packer.generate()
particleList = packer.getSimpleSphereCollection()
#tag particles along base and top of rectangle
#then add the particles to the simulation object:
for pp in particleList:
centre = pp.getPosn()
radius = pp.getRadius()
Y = centre[1]
if (Y < 1.0): # particle is near the base (tag=2)
pp.setTag (2)
elif (Y > 9.0): # particle is near the top (tag=3)
pp.setTag (3)
else: # particle is inside the shear cell (tag=1)
pp.setTag (1)
sim.createParticle(pp) # add the particle to the simulation object
#set the density of all particles:
sim.setParticleDensity (
tag = 1,
mask = -1,
Density = 100.0
)
sim.setParticleDensity (
tag = 2,
mask = -1,
Density = 100.0
)
sim.setParticleDensity (
tag = 3,
mask = -1,
Density = 100.0
)
#add driving walls above and below the particle assembly:
sim.createWall (
name = "bottom_wall",
posn = Vec3 (0,0,0),
normal = Vec3 (0,1,0)
)
sim.createWall (
name = "top_wall",
posn = Vec3 (0,10,0),
normal = Vec3 (0,-1,0)
)
#unbonded particle-pairs undergo frictional interactions:
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 135
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
)
)
#add local damping to avoid accumulating kinetic energy:
sim.createInteractionGroup (
LinDampingPrms (
name = "damping",
viscosity = 1.0,
maxIterations = 100
)
)
#add ServoWallLoaderRunnables to apply constant normal stress:
servo_loader1 = ServoWallLoaderRunnable(
LsmMpi = sim,
interactionName = "twall_bonds",
force = Vec3 (0.0, -1000.0, 0.0),
startTime = 0,
rampTime = 5000
)
sim.addPreTimeStepRunnable (servo_loader1)
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)
#add a FieldSaver to store total kinetic energy:
sim.createFieldSaver (
ParticleScalarFieldSaverPrms(
fieldName="e_kin",
fileName="ekin.dat",
fileFormat="SUM",
beginTimeStep=0,
endTimeStep=100000,
timeStepIncr=1
)
)
#add FieldSavers to store wall forces and positions:
posn_saver = WallVectorFieldSaverPrms(
wallName=["bottom_wall", "top_wall"],
fieldName="Position",
fileName="out_Position.dat",
fileFormat="RAW_SERIES",
beginTimeStep=0,
endTimeStep=100000,
timeStepIncr=1
)
sim.createFieldSaver(posn_saver)
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)
#add a CheckPointer to store simulation data:
sim.createCheckPointer (
CheckPointPrms (
fileNamePrefix = "snapshot",
beginTimeStep = 0,
endTimeStep = 100000,
timeStepIncr = 5000
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 137
)
)
#execute the simulation:
sim.run()
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 138
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 *
class ServoWallLoaderRunnable (Runnable):
def __init__ (self,
LsmMpi=None,
interactionName=None,
force=Vec3(0,0,0),
startTime=0,
rampTime = 200
):
"""
Subroutine to initialise the Runnable and store parameter values.
"""
Runnable.__init__(self)
self.sim = LsmMpi
self.interactionName = interactionName
self.force = force
self.dt = self.sim.getTimeStepSize()
self.rampTime = rampTime
self.startTime = startTime
self.Nt = 0
def run (self):
"""
Subroutine to apply the force to a wall interaction. After self.startTime
timesteps, the force on the wall increases linearly over
self.rampTime timesteps until the desired wall force is achieved.
Thereafter the wall force is kept fixed.
"""
if (self.Nt > self.startTime):
#compute the slowdown factor if still accelerating the wall:
if (self.Nt < (self.startTime + self.rampTime)):
f = float(self.Nt - self.startTime) / float(self.rampTime)
else:
f = 1.0
#compute the amount by which to move the wall this timestep:
Dforce = Vec3(
f*self.force[0],
f*self.force[1],
f*self.force[2]
)
#instruct the simulation to apply the prescribed force to the wall:
self.sim.applyForceToWall (self.interactionName, Dforce)
self.Nt += 1
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 139
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 140
A.2 GenGeo examples
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 141
A.2.1 simple box.py
# --- geometry setup script for simple box ---
from gengeo import *
# -- parameters --
# block dimensions
xdim=10
ydim=20
zdim=10
# particle size range
minRadius = 0.2
maxRadius = 1.0
# ---------------------
# 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)
# pack particles into volume
packer.generatePacking(box,mntable)
# create bonds between neighbouring particles:
mntable.generateBonds(0,1.0e-5,0)
# write a geometry file
mntable.write("box.geo",1)
mntable.write("box.vtu",2)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 142
A.2.2 smooth box.py
# --- geometry setup script for block with smooth sides ---
from gengeo import *
# - input parameters --
# block dimensions
xdim=10
ydim=20
zdim=10
# particle size range
minRadius = 0.2
maxRadius = 1.0
# ---------------------
# 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))
# add them to the box
box.addPlane(bottomPlane)
box.addPlane(leftPlane)
box.addPlane(frontPlane)
box.addPlane(topPlane)
box.addPlane(rightPlane)
box.addPlane(backPlane)
# -- 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
# pack particles into volume
packer.generatePacking(box,mntable,0,1)
# create bonds between neighbouring particles:
mntable.generateBonds(0,1.0e-5,0)
# calculate and print the porosity:
volume = xdim*ydim*zdim
porosity = (volume - mntable.getSumVolume(groupID=0))/volume
print "Porosity: ", porosity
# write a geometry file
mntable.write("smooth_box.geo", 1)
mntable.write("smooth_box.vtu", 2)
APPENDIX A. CODE-LISTINGS FOR TUTORIAL EXAMPLES 144
A.2.3 cluster box.py
# --- geometry setup script for block with clustered particles ---
from gengeo import *
# - input parameters --
# block dimensions
xdim=10
ydim=20
zdim=10
# particle size range
minRadius = 0.2
maxRadius = 1.0
# ---------------------
# 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))
# add them to the box
box.addPlane(bottomPlane)
box.addPlane(leftPlane)
box.addPlane(frontPlane)
box.addPlane(topPlane)
box.addPlane(rightPlane)
box.addPlane(backPlane)
# -- 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
# pack particles into volume
packer.generatePacking(box,mntable,0,1)
# -- set up clustering seeds in a regular grid
# cluster parameters
ncluster_x=3
ncluster_y=6
ncluster_z=3
dx=xdim/float(ncluster_x)
dy=ydim/float(ncluster_y)
dz=zdim/float(ncluster_z)
# 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)
# -- tag particle according to nearest seed
mntable.tagParticlesToClosest(0,1)
# generate bonds
mntable.generateClusterBonds(0,1.0e-5,1,2)
# remove dummy particles before writing files
mntable.removeParticlesInGroup(1)
# write a geometry file
mntable.write("cluster_box.geo", 1)
mntable.write("cluster_box.vtu", 2)
Appendix B
Tables of ESyS-Particle Interaction
Groups and Field Savers
146
APPENDIX B. INTERACTION GROUPS & FIELDS 147
B.1 Interaction Groups
NRotSphere RotSphere RotThermalSphere
Gravity
Damping
Body Forces
LinDamping
RotDamping
Unbonded
Particle-Particle
NRotElastic RotElastic RotThermalElastic
Interactions
NRotFriction RotFriction RotThermalFriction
Bonded
Particle-Particle NRotBond RotBond RotThermalBond
Interactions
Unbonded NRotElasticWall
Wall-Particle NRotElasticLinMesh
Interactions NRotElasticTriMesh
NRotBondedWall
Bonded
NRotBondedLinMesh
Wall-Particle
NRotBondedTriMesh
Interactions
NRotSoftBondedWall
APPENDIX B. INTERACTION GROUPS & FIELDS 148
B.2 Field Saver Field Names for Common Particle
Types and Interaction Groups
Scalar Field Name Vector Field Name
id
tag
radius
v abs
displacement
NRotSphere
sigma xx 2d
position
Particles
RotSphere
sigma xy 2d
force
sigma yy 2d
velocity
sigma d
e kin
e kin linear
RotSphere
e kin rot
ang velocity
Position
Walls NRotElasticWall
Force
Damping
RotDamping
dissipated energy force
NRotElastic
RotElastic
count
force
RotThermalElastic
potential energy
count
sticking
NRotFriction
slipping force
RotFriction
force decit normal force
Interactions
RotThermalFriction
dissipated energy
potential energy
frictional force
RotFriction
tangential force
NRotBond strain
NRotBond count
RotBond breaking criterion force
RotThermalBond potential energy
e pot normal
RotBond e pot shear
RotThermalBond e pot twist
e pot bend
normal force
RotBond
tangential force
Appendix C
File Formats
149
APPENDIX C. FILE FORMATS 150
C.1 Descriptions and Output File Formats for Field
Saver Field Names
Field Name Description Output Format
id Unique identier for a particle
tag Identier to group subsets of particles
radius Sphere radius
v abs Norm of velocity SILO
Particle
sigma xx 2d XX component of stress tensor SUM
Scalar
sigma xy 2d XY component of stress tensor MAX
Field
sigma yy 2d YY component of stress tensor RAW SERIES
Savers
sigma d Norm of deviatoric stress tensor RAW WITH POS ID
e kin Total kinetic energy
e kin linear Linear kinetic energy
e kin rot Rotational kinetic energy
displacement Displacement from initial position
SILO
Particle
position Position of particle
SUM
Vector
force Force acting on particle
MAX
Field
velocity Linear velocity
RAW2
Savers
ang velocity Angular velocity
RAW SERIES
RAW WITH ID
Wall
Vector
Position Position of wall
SILO
Field RAW SERIES
Savers
Force Force acting on wall
count Number of interactions between particles
sticking Number of static friction interactions
slipping Number of dynamic friction interactions
strain Nonrotational bond strain SUM
Interaction breaking criterion Value needed to break a bond MAX
Scalar force decit Additional force needed for slipping RAW
Field dissipated energy Energy dissipated from slipping RAW2
Savers potential energy Total potential energy RAW WITH ID
e pot normal Normal potential energy RAW WITH POS ID
e pot shear Shear potential energy
e pot twist Tortional potential energy
e pot bend Bending potential energy
Interaction SUM
Vector
force Force acting between particles
RAW2
Field RAW WITH ID
Savers
normal force Normal component of force
RAW WITH POS ID
APPENDIX C. FILE FORMATS 151
C.2 Descriptions of the Output File Formats for Field
Savers
The current FieldSaver infrastructure supports at the moment a total 10 dierent output
formats. Three of those formats (DX, POV and SILO) are for use with specic visu-
alisation software, the other 7 (RAW, RAW2, RAW SERIES, RAW WITH ID,
RAW WITH POS ID, SUM and MAX) are plain ASCII formats writing dierent
amounts of data. Data in multi-colums les are always separated by spaces. Below is a
description of those formats. N.B. column indices given below start at 1, not at 0 as in C
or python notation.
C.2.1 RAW
This format produces one le per saved time step, containing a particle position and the
eld value for each eld value. In case of a ParticleFieldSaver the position is that of the
particle, in case of a InteractionFieldSaver its the location of the rst particle (i.e. the one
with the lower ID)
1
involved in the interaction. The les therefore consist of 4 columns
in case of a scalar eld px py pz v or 6 colums in case of a vector eld px py pz vx vy
vz where
px py pz in columns 1, 2, 3 is the position of the particle with the lower id as
described above
v, in column 4 or in case of a vector eld, vx vy vz in columns 4, 5, 6, represent
the value of the saved eld
C.2.2 RAW2
This format produces one le per saved time step,
C.2.3 RAW SERIES
This format produces a single le for the whole simulation. This le contains one row per
saved time step and either one column per eld value in case of a scalar eld or 3 columns
per eld value in case of a vector eld. This implies that with the RAW SERIES format
the location of the eld value is not saved. Typical
C.2.4 RAW WITH ID
C.2.5 RAW WITH POS ID
The RAW WITH POS ID format is only applicable to interaction savers
2
. It produces
one le per saved time step, containing the particle IDs, particle positions, interaction
position and the eld value for each interaction. The les therefore consist 1 row per
interaction having 12 columns in case of a scalar eld id1 id2 p1x p1y p1z p2x p2y
p2z ipx ipy ipz v or 14 colums in case of a vector eld id1 id2 p1x p1y p1z p2x p2y
p2z ipx ipy ipz vx vy vz where
1
It would be possible to change this to the actual interaction location - which would make more sense
2
up to rev. 1100 there is a bug which prevents RAW WITH POS ID from working with scalar inter-
action elds.
APPENDIX C. FILE FORMATS 152
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
rst 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 eld, vx vy vz in columns 12, 13, 14, represent
the value of the saved eld for the interaction
C.2.6 SUM
C.2.7 MAX