Impose force/velocity

Motion integration is performed by the Leapfrog engine and described in detail in Motion integration. Usually, each node moves according to force, which results in acceleration, velocity change and position change; nodes rotate according to torque, which results in angular acceleration, angular velocity change and orientation change.

It is sometimes useful to prescribe some other kind of motion – e.g. force, velocity or trajectory.

Note

Motion integration is performed on nodes, which are usually attached to particles. There are also nodes not attached to any particle but used by some engines (e.g. Conveyor); those nodes, if added S.dem.nodes (usually via S.dem.nodesAppend), will move just the same. This will be shown below.

Blocking DoFs

Blocking degrees of freedom (DoFs) is done via the fixed parameter to routines creating particles (Sphere.make and friends). This parameter simply sets DemData.blocked. Wall, InfCylinder and Facet are by default fixed (they will not move, even if force is applied to them); in contrast to that, particles like Sphere or Capsule are by default not fixed.

DemData.blocked is in itself more versatile; it can block forces/torques along individual axes; translations are noted as xyz (lowercase) and rotations XYZ (uppercase). For instance

  • to prevent particle from rotating, set blocked to XYZ;
  • for 2d simulation in the \(xy\)-plane, set blocked to zXY (prevent out-of-plane translation and only allow rotation along the axis perpendicular to \(xy\) plane).

Note

blocked does not make the node necessarily unmovable. Since forces/torques along that axis are ignored, it only means that velocity and angular velocity will be constant. Those are initially zero, but if you set them to a non-zero value, the node will be moving.

Woo[1]: s=Sphere.make((0,0,1),.1)

# see what is blocked on the sphere
Woo[2]: s.blocked
Out[2]: ''

Woo[3]: s.blocked='XYZ'

# did it really change?
Woo[4]: s.blocked
Out[4]: 'XYZ'

Woo[5]: w=Wall.make(0,axis=2)

# see what is blocked on the wall by default
Woo[6]: w.blocked
Out[6]: 'xyzXYZ'

# Particle.blocked is only a shorthand for particles with a single node
# it really means this:
Woo[7]: w.nodes[0].dem.blocked
Out[7]: 'xyzXYZ'

# DemData is what carries all DEM-related properties (velocity, mass, inertia and such)
Woo[8]: w.nodes[0].dem
Out[8]: <DemData @ 0x3480690>

# let's dump it to see what's inside there
Woo[9]: w.nodes[0].dem.dumps(format='expr')
Out[9]: '##woo-expression##\n#: import woo.dem\nwoo.dem.DemData(\n\tvel=(0, 0, 0),\n\tangVel=(0, 0, 0),\n\tmass=0,\n\tinertia=(0, 0, 0),\n\tforce=(0, 0, 0),\n\ttorque=(0, 0, 0),\n\tangMom=(nan, nan, nan),\n\tflags=63,\n\tlinIx=-1,\n\timpose=None\n)\n'

DemData.impose

A more versatile option is to use the DemData.impose to assign any object deriving from woo.dem.Impose. What is imposed in each step is either force or velocity, though those are not constrained to global coordinate axes, and are computed by some c++ routine. Impose instances can be shared by many nodes.

This shows how the feed was made movable by imposing woo.dem.HarmonicOscillation on the woo.dem.ConveyorInlet.node.

This example shows the woo.dem.AlignedHarmonicOscillations imposition, which is applied to all cylinders with different parameters:

import woo
from woo import utils,pack
from woo.dem import *
from woo.core import *
from minieigen import *

woo.master.scene=S=Scene(fields=[DemField(gravity=(0,0,-10))])
mat=utils.defaultMaterial()
sp=pack.SpherePack()
sp.makeCloud((4,4,4),(14,14,14),.4,rRelFuzz=.5)
sp.toSimulation(S,mat=mat)
S.dem.par.add([utils.wall(1,axis=2,sense=0,mat=mat,glAB=((-10,-1),(20,11))),])
S.periodic=True
S.cell.setBox(20,20,20)
S.engines=DemField.minimalEngines(damping=.4)
S.dtSafety=.5
S.dt=.5*utils.pWaveDt() # to compute oscillation freqs below
# create cylinders
for i,x in enumerate([-2,0,2,4,6,8,10.5,12,14]):
    c=InfCylinder.make((x,0,3),radius=.8,axis=1,mat=mat,glAB=(-1,11))
    c.angVel=(0,2.*(i+1),0)
    # each of cylinders will move haronically along global x and z axes (not y)
    c.impose=AlignedHarmonicOscillations(freqs=(1./(10000.*S.dt),float('nan'),1/(((i%3)+3)*1000.*S.dt)),amps=(.3*(i%2+1),0,.4*(i%4+1)))
    S.dem.par.add(c)
S.saveTmp()

try:
    from woo import gl
    gl.Gl1_Wall.div=10
    gl.Gl1_InfCylinder.wire=True
except ImportError: pass

Interpolated

woo.dem.InterpolatedMotion was used in this example to move the bottle (using Video, internally woo.qt.SnapshotEngine); since we needed to move the whole bottle as an aggregate, it was added as a clump (download pill-bottle.coarse2.stl):

The same simulation visualized in Paraview using the woo.dem.VtkExport engine:

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

Circular

Todo

write about woo.dem.CircularOrbit and how to use it on meshes (like mixer blades), be those clumped or not.

Tip

Got questions? Ask at ask.woodem.org. Report issues to github.