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
toXYZ
;for 2d simulation in the \(xy\)-plane, set
blocked
tozXY
(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 @ 0x33a47d0>
# 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
Report issues or inclarities to github.