# 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:

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])
```

### Inlet¶

## 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\):

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.