Direct Simulation Interface

BotOp is a narrow control interface to a real or simulated robot, which is also real time and threaded (as for a real robot). However, sometimes we need a more low-level interface to a physical simulator, e.g. to implement a Reinforcement Learning environment.

Minimalistic example

Let’s first create the smallest possible example: A dropping ball. All we have to do is create a configuration with a ball, create an “attached” simulation, and step it:

[1]:
from robotic import ry
import time
[2]:
# minimalistic configuration
C = ry.Config()
C.addFrame('ball') .setShape(ry.ST.sphere, [.2]) .setMass(.1) .setPosition([0,0,1])
C.view()

S = ry.Simulation(C, ry.SimulationEngine.physx, verbose=0)

tau=.01
for i in range(200):
    time.sleep(tau)
    S.step([], tau,  ry.ControlMode.none)
    C.view()

Note that a Simulation is directly operating on the given configuration C. E.g., when you step the simulation, it changes the state of C. In that sense, the simulation is rigidly associated/attached to C. (This is different to BotOp, where the real robot (or simulation) is separate from C and you have to explicitly sync them. It’s also different from providing C in a constructor of KOMO, as KOMO creates it’s own copies of configurations. The simulation class doesn’t copy C, it operates directly on it.)

There are a number of (global) parameters used when creating a simulation. As always, we can see which parameters were queried by params_print:

[3]:
ry.params_print()
-- ry.cpp:operator():86(0) python,
message: "this parameter was loaded from 'rai.cfg'",
physx/verbose: 1,
physx/yGravity!,
physx/softBody!,
physx/multiBody,
physx/multiBodyDisableGravity,
physx/jointedBodies!,
physx/angularDamping: 0.1,
physx/defaultFriction: 1,
physx/defaultRestitution: 0.1,
physx/motorKp: 1000,
physx/motorKd: 100

And params_add allows you to set parameters. (Actually append, which is why we first need to clear.)

[4]:
ry.params_clear()
ry.params_add({'physx/defaultRestitution': 1.})
ry.params_print()
-- ry.cpp:operator():86(0) physx/defaultRestitution: 1

Let’s try again:

[5]:
del S
C.getFrame('ball') .setPosition([0,0,1])
S = ry.Simulation(C, ry.SimulationEngine.physx, verbose=0)

tau=.01
for i in range(200):
    time.sleep(tau)
    S.step([], tau,  ry.ControlMode.none)
    C.view()
-- simulation.cpp:~Simulation:148(0) shutting down Simulation

As you can see, “restitution” means bouncing. Below is an example of how to set bounciness and friction per object.

[6]:
del S
del C
ry.params_clear()
-- simulation.cpp:~Simulation:148(0) shutting down Simulation

Including robots/articulated configurations

Let’s first give a basic example, pushing a block, which uses default settings and a predefined robot:

[7]:
from robotic import ry
import numpy as np
import time
[8]:
C = ry.Config()
C.addFile(ry.raiPath('scenarios/pandaSingle.g'))
C.view(False)

C.addFrame('box') \
    .setShape(ry.ST.ssBox, size=[.1,.1,.1,.005]) .setColor([1,.5,0]) \
    .setPosition([.1,.35,.9])

C.addFrame('stick', 'l_gripper') \
    .setShape(ry.ST.capsule, size=[.3,.02]) .setColor([.5,1,0]) \
    .setRelativePosition([0,0,-.13])

C.setJointState([.0], ['l_panda_joint2']) #only cosmetics
C.setJointState([.02], ['l_panda_finger_joint1']) #only cosmetics

q0 = C.getJointState()
X0 = C.getFrameState()

C.view()
[8]:
0
[9]:
S = ry.Simulation(C, ry.SimulationEngine.physx, verbose=0)
[10]:
def move_a_bit(T=100):
    tau = .01
    q = q0.copy()
    for i in range(T):
        time.sleep(tau)
        q[0] = q[0] - tau*1.
        S.step(q, tau,  ry.ControlMode.position)
        C.view()
[11]:
move_a_bit()
[12]:
del S
C.setFrameState(X0)
C.getFrame('box') .setMass(.1)
S = ry.Simulation(C, ry.SimulationEngine.physx, verbose=0)
move_a_bit()
-- simulation.cpp:~Simulation:148(0) shutting down Simulation
[13]:
del S
C.setFrameState(X0)
S = ry.Simulation(C, ry.SimulationEngine.kinematic, verbose=0)
move_a_bit()
-- simulation.cpp:~Simulation:148(0) shutting down Simulation
[14]:
del S
C.setFrameState(X0)
C.getFrame('box') .setColor([1,1,0,.5])
S = ry.Simulation(C, ry.SimulationEngine.physx, verbose=0)
move_a_bit()
-- simulation.cpp:~Simulation:148(0) shutting down Simulation
[15]:
del S
C.setFrameState(X0)
C.getFrame('box') .setColor([1,1,0,1]) .setParent(C.getFrame('table'))
S = ry.Simulation(C, ry.SimulationEngine.physx, verbose=0)
move_a_bit()
-- simulation.cpp:~Simulation:148(0) shutting down Simulation
-- WARNING:kin_physx.cpp:prepareLinkShapes:626(-1) computing compound inertia for object frame 'world' -- this should have been done earlier?
[16]:
del S
del C
-- simulation.cpp:~Simulation:148(0) shutting down Simulation

Friction and bouncing per object

Let’s build a configuration manually to test varying friction and bouncing (=restitution) per object:

[17]:
from robotic import ry
import time
[18]:
C = ry.Config()
C.addFrame('table') .setShape(ry.ST.ssBox, [2., 1., .1, .02]) .setColor([.3]) \
    .setPosition([0,0,.3]) .setQuaternion([1,-.25,0,0])

for i in range(10):
    f = C.addFrame(f'block_{i}')
    f.setShape(ry.ST.ssBox, [.1,.2,.1,.02]) .setColor([1,.1*i,1-.1*i])
    f.setPosition([.7 - .15*i,-.2,1.])
    f.setMass(1.)
    f.setAttribute('friction', .05*i)

for i in range(10):
    f = C.addFrame(f'ball_{i}')
    f.setShape(ry.ST.sphere, [.05]) .setColor([1,.1*i,1-.1*i])
    f.setPosition([.7 - .15*i,.2,1.])
    f.setMass(.2)
    f.setAttribute('restitution', .5+.1*i)
C.view()
[18]:
0
[19]:
S = ry.Simulation(C, ry.SimulationEngine.physx, verbose=0)

tau=.01
for i in range(200):
    time.sleep(tau)
    S.step([], tau,  ry.ControlMode.none)
    C.view()
[20]:
S=0
C=0
-- simulation.cpp:~Simulation:148(0) shutting down Simulation

Resetting and messing with state

[21]:
from robotic import ry
import time
[22]:
C = ry.Config()

for i in range(5):
    f = C.addFrame(f'block_{i}')
    f.setShape(ry.ST.ssBox, [.2,.3,.2,.02]) .setColor([1,.2*i,1-.2*i])
    f.setPosition([0,0, .25*(i+1)])
    f.setMass(.1)

C.addFrame('base') .setPosition([1., 0, .5]) .addAttributes({'multibody': True})

C.addFrame('finger', 'base') .setShape(ry.ST.ssBox, [.3, .1, .1, .02]) .setColor([.9]) \
    .setMass(.1) \
    .setJoint(ry.JT.transX)

q0 = C.getJointState()
X0 = C.getFrameState()

C.view()
[22]:
0
[23]:
S = ry.Simulation(C, ry.SimulationEngine.physx, 1)
[24]:
def move_a_bit():
    tau = .01
    q = q0.copy()
    for i in range(100):
        time.sleep(tau)
        q[0] = q[0] - tau*1.
        S.step(q, tau,  ry.ControlMode.position)
        C.view()

move_a_bit()

We are in the middle of some action. Let’s swap two blocks (top/bottom). And also displace the bottom one:

[25]:
#swap two blocks
X = C.getFrameState()
A = X[0,:].copy()
X[0,:] = X[4,:]
X[4,:] = A
X[4,1] = .2
C.setFrameState(X)
C.view()
[25]:
0

If you compare the simulation display with the configuration display, you see the difference. Now, a simulation allows you to push the configuration back into the simulation, overwriting the physical state:

[26]:
S.setState(X)
#updates the simulation window
[27]:
#recall that the position control starts from zero, leading to a hard PD jerk initially
move_a_bit()

We can also recreate the initial state:

[28]:
S.setState(X0, q0)
C.view()
[28]:
0
[29]:
move_a_bit()

Internally, not only the frame state (pose of all dynamic objects), but also the joint state (motor states/targets) are overwritten in the physics simulator. The optional arguments to setState allow you to also set the current frame and joint velocities. (Frame velocities is a n-by-2-by-3 tensor, as you get it from getState.)

[30]:
del S
del C
-- simulation.cpp:~Simulation:148(0) shutting down Simulation
[ ]: