Clumps

Clumps are rigid aggregates of particles particles. They move in such way that particle’s arrangement is preserved. Clumps are not particles by themselves, they don’t collide with particles: only the particles they are composed of do. Forces applied on particles are subsequently transferred to the clump, which moves as a single body, and particles’ positions and velocities are then update so that the configuration remains the same.

Internally, clump is a Node with some special data attached (ClumpData). ClumpData derives from DemData (which contains things like mass, vel, … needed for motion integration) and adds information about particles it is composed of (also their relative positions and orientations).

Dynamic properties

Dynamic properties such as mass and inertia are computed automatically, either analytically or numerically (grid sampling).

Analytic

This algorithm is useful for non-intersecting (or lightly intersecting) configurations since the volumes of overlap are computed twice.

Dynamic properties are computed by looping over all nodes (which carry the mass of particles — this means that clumped particles can be multi-nodal) of the clump. Using \(\vec{x}_i\), \(\quat{q}_i\), \(m_i\), \(\vec{I}_i\) to respectively denote position, orientation, mass and local principal inertia, we compute in global coordinates mass, static momentum and inertia tensor as:

\begin{align*} m_g&=\sum_i m_i, \\ \vec{S}_g&=\sum_i m_i \vec{x}_i, \\ [\tens{I}_g]&=\sum_i f_{it}(f_{ir}(\vec{I}_i,\quat{q}_i^*),m_i,-\vec{x}_i), \end{align*}

where \(f_{it}\) is function translating tensor of inertia using the parallel axis theorem (\([\tens{I}_{gi}]=[\tens{I}_{li}]+m_i(\vec{x}_i^T\vec{x}_i \mat{I}_{3\times3}-\vec{x}_i\vec{x}_i^T)\), with \(\tens{I}_{li}\) being local inertia tensor but in global orientation) and \(f_{ir}\) is function rotation the tensor of inertia using rotation matrix \(\mat{T}\) (which is computed from the \(\quat{q}^*\) conjugated orientation, since we rotate backwards, from local to global) as \(\mat{T}^T[\tens{I}]\mat{T}\)).

Todo

Formulas for computing principal axes position & orientation.

Grid sampling

This method is used when computing properties of woo.dem.SphereClumpGeom.

Todo

Describe the grid sampling algorithm.

Building clumps

Individual

Using S.dem.addClumped instead of S.dem.add, list of particles is clumped together before being added to the simulation. Dynamic clump properties are computed analytically in this case. This can be useful for importing mesh which is not static, but should behave as a rigid objects, as seen e.g. in the bottle example with the bottle (download pill-bottle.coarse2.stl):

from woo.core import *
from woo.dem import *
import woo, math
from minieigen import *
# use the same material for both capsules and boundaries, for simplicity
capsMat=wallMat=woo.dem.FrictMat(ktDivKn=.2,tanPhi=.2,density=1000,young=1e7)
# create the scene object, set the master scene
S=woo.master.scene=Scene(fields=[DemField(gravity=(0,0,-10))])
# add the bottom plane (wall)
S.dem.par.add(Wall.make(0,axis=2,sense=1,mat=wallMat,color=0,glAB=((-.2,-.2),(.2,.2))))
# create bottle mesh from the STL
bottle=woo.utils.importSTL('pill-bottle.coarse2.stl',mat=wallMat,scale=0.001,shift=(.056,.027,-0.01),ori=Quaternion((1,0,0),math.pi/2.),color=-.25)
# create node which will serve as "handle" to move the bottle
S.lab.botNode=Node(pos=(0,0,.04),dem=ClumpData(blocked='xyzXYZ'))
# add bottle as clump;
# center is the centroid normally, but the mesh has no mass, thus reference point must be given
S.dem.par.addClumped(bottle,centralNode=S.lab.botNode)

S.dtSafety=.5

import woo.gl
woo.gl.Renderer.engines=False
woo.gl.Renderer.allowFast=False

# particle factory, with many parameters
# when the factory finishes, it will call the pillsDone function, defined below
factory=CylinderInlet(stepPeriod=100,node=Node(pos=(0,0,.17),ori=Quaternion((0,1,0),math.pi/2.)),radius=.018,height=.05,generator=PharmaCapsuleGenerator(),materials=[capsMat],massRate=0,maxMass=.12,label='feed',attemptPar=100,color=-1,doneHook='pillsDone(S)')

# add factory (and optional Paraview export) to engines
S.engines=DemField.minimalEngines(damping=.3)+[
    factory,
    ## comment out to enable export for Paraview:
    VtkExport(out='/tmp/bottle.',ascii=True,stepPeriod=500) 
]
# save the scene
S.saveTmp()

def pillsDone(S):
    # once the bottle is full, wait for another 0.2s
    #then start moving the bottle by interpolating prescribed positions and orientations
    S.lab.botNode.dem.impose=InterpolatedMotion(t0=S.time+0.2,poss=[S.lab.botNode.pos,(0,.05,.08),(0,.05,.09),(0,.04,.13)],oris=[Quaternion.Identity,((1,0,0),.666*math.pi),((1,0,0),.85*math.pi),((1,0,0),.9*math.pi)],times=[0,.3,.5,1.6])

Clump motion

Gravity

Particles participating in a clump are exempt from direct gravity application, since gravity is applied on the clump node itself already.

Contact forces

Clumps move just like any other nodes; contrary to particles, they have no direct (contact) forces applied on them so those must be collected in the integrator (by calling woo.dem.ClumpData.forceTorqueFromMembers); it is a simple summation of forces but related to the clump node’s position \(\vec{x}_c\):

\begin{align*} F_{\Sigma}&=\sum \vec{F}_i, \\ T_{\Sigma}&=\sum \vec{T}_i+(\vec{x}_i-\vec{x}_c)\times\vec{F}_i. \end{align*}

These summary forces are used in Motion integration.

Note

Forces on clumps are collected in the integrator for the purposes of motion integration and usually (when woo.dem.Leapfrog.reset is True, which is the highly recommended default) immediately discarded again; that means that the summary force is not easily accessible for the user. The function woo.dem.ClumpData.forceTorqueFromMembers can be used to compute force and torque from members on-demand. That is useful e.g. for plotting acting force; note however that the value returned from forceTorqueFromMembers does not include gravity acting on the clump as whole, as per notice above.

Woo[1]: from woo.core import *; from woo.dem import *

Woo[2]: S=woo.core.Scene(fields=[DemField(gravity=(0,0,-10))],engines=DemField.minimalEngines(),dt=1e-5)

Woo[3]: S.dem.par.add(Wall.make(0,axis=2,sense=1))              # ground
Out[3]: 0

Woo[4]: n=S.dem.par.addClumped([Sphere.make((0,0,0),.5),Sphere.make((0,0,1),.5),Sphere.make((0,1,0),.5)])  # clump of 3 spheres

Woo[5]: n.pos                                                   # clump node is in the centroid
Out[5]: Vector3(0,0.3333333333333333,0.3333333333333333)

Woo[6]: S.dem.par[0].mass, S.dem.par[1].mass, S.dem.par[2].mass # mass of individual particles
Out[6]: (0.0, 523.5987755982989, 523.5987755982989)

Woo[7]: n.dem.mass                                              # clump mass is the sum
Out[7]: 1570.7963267948967

Woo[8]: S.run(1000,True)                                        # run a few steps so that the clump reaches the ground

Woo[9]: S.dem.par[0].f, S.dem.par[1].f, S.dem.par[2].f          # contact force on individual particles
Out[9]: 
(Vector3(2.1686873241991673e7,6.026791875440375e7,-6.402979849407127e9),
 Vector3(-7137.042700661346,-96446.16342191002,2324835.5974078057),
 Vector3(0,0,0))

Woo[10]: ClumpData.forceTorqueFromMembers(n)                     # force and torque on the whole clump
Out[10]: 
(Vector3(-68147.09463492269,-191520.48658224195,3567783.485325047),
 Vector3(104543.7678726993,-4457.2178245989335,37743.936255249326))

Tip

Report issues or inclarities to github.