Skip to content

Commit 6876369

Browse files
authored
Merge pull request #191 from thowell/inertia_box_fluid_model
inertia box fluid model
2 parents 406d46b + 7a3c74e commit 6876369

File tree

6 files changed

+293
-70
lines changed

6 files changed

+293
-70
lines changed

mujoco_warp/_src/io.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
4747
if mjm.tendon_frictionloss.any():
4848
raise NotImplementedError("Tendon frictionloss is unsupported.")
4949

50+
if mjm.geom_fluid.any():
51+
raise NotImplementedError("Ellipsoid fluid model not implemented.")
52+
5053
# check options
5154
for opt, opt_types, msg in (
5255
(mjm.opt.integrator, types.IntegratorType, "Integrator"),
@@ -56,12 +59,6 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
5659
if opt not in set(opt_types):
5760
raise NotImplementedError(f"{msg} {opt} is unsupported.")
5861

59-
if mjm.opt.wind.any():
60-
raise NotImplementedError("Wind is unsupported.")
61-
62-
if mjm.opt.density > 0 or mjm.opt.viscosity > 0:
63-
raise NotImplementedError("Fluid forces are unsupported.")
64-
6562
# TODO(team): remove after solver._update_gradient for Newton solver utilizes tile operations for islands
6663
nv_max = 60
6764
if mjm.nv > nv_max and mjm.opt.jacobian == mujoco.mjtJacobian.mjJAC_DENSE:
@@ -280,6 +277,9 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
280277
tolerance=mjm.opt.tolerance,
281278
ls_tolerance=mjm.opt.ls_tolerance,
282279
gravity=wp.vec3(mjm.opt.gravity),
280+
wind=wp.vec3(mjm.opt.wind[0], mjm.opt.wind[1], mjm.opt.wind[2]),
281+
density=mjm.opt.density,
282+
viscosity=mjm.opt.viscosity,
283283
cone=mjm.opt.cone,
284284
solver=mjm.opt.solver,
285285
iterations=mjm.opt.iterations,
@@ -568,6 +568,7 @@ def make_data(mjm: mujoco.MjModel, nworld: int = 1, nconmax: int = -1, njmax: in
568568
ctrl=wp.zeros((nworld, mjm.nu), dtype=float),
569569
qfrc_applied=wp.zeros((nworld, mjm.nv), dtype=float),
570570
xfrc_applied=wp.zeros((nworld, mjm.nbody), dtype=wp.spatial_vector),
571+
fluid_applied=wp.zeros((nworld, mjm.nbody), dtype=wp.spatial_vector),
571572
eq_active=wp.array(np.tile(mjm.eq_active0, (nworld, 1)), dtype=bool),
572573
mocap_pos=wp.zeros((nworld, mjm.nmocap), dtype=wp.vec3),
573574
mocap_quat=wp.zeros((nworld, mjm.nmocap), dtype=wp.quat),
@@ -605,6 +606,7 @@ def make_data(mjm: mujoco.MjModel, nworld: int = 1, nconmax: int = -1, njmax: in
605606
qfrc_spring=wp.zeros((nworld, mjm.nv), dtype=float),
606607
qfrc_damper=wp.zeros((nworld, mjm.nv), dtype=float),
607608
qfrc_gravcomp=wp.zeros((nworld, mjm.nv), dtype=float),
609+
qfrc_fluid=wp.zeros((nworld, mjm.nv), dtype=float),
608610
qfrc_passive=wp.zeros((nworld, mjm.nv), dtype=float),
609611
subtree_linvel=wp.zeros((nworld, mjm.nbody), dtype=wp.vec3),
610612
subtree_angmom=wp.zeros((nworld, mjm.nbody), dtype=wp.vec3),
@@ -854,6 +856,7 @@ def padtile(x, length, dtype=None):
854856
ctrl=tile(mjd.ctrl),
855857
qfrc_applied=tile(mjd.qfrc_applied),
856858
xfrc_applied=tile(mjd.xfrc_applied, dtype=wp.spatial_vector),
859+
fluid_applied=wp.zeros((nworld, mjm.nbody), dtype=wp.spatial_vector),
857860
eq_active=tile(mjd.eq_active.astype(bool)),
858861
mocap_pos=tile(mjd.mocap_pos, dtype=wp.vec3),
859862
mocap_quat=tile(mjd.mocap_quat, dtype=wp.quat),
@@ -891,6 +894,7 @@ def padtile(x, length, dtype=None):
891894
qfrc_spring=tile(mjd.qfrc_spring),
892895
qfrc_damper=tile(mjd.qfrc_damper),
893896
qfrc_gravcomp=tile(mjd.qfrc_gravcomp),
897+
qfrc_fluid=tile(mjd.qfrc_fluid),
894898
qfrc_passive=tile(mjd.qfrc_passive),
895899
subtree_linvel=tile(mjd.subtree_linvel, dtype=wp.vec3),
896900
subtree_angmom=tile(mjd.subtree_angmom, dtype=wp.vec3),
@@ -1071,12 +1075,14 @@ def get_data_into(
10711075
result.cvel = d.cvel.numpy()[0]
10721076
result.cdof_dot = d.cdof_dot.numpy()[0]
10731077
result.qfrc_bias = d.qfrc_bias.numpy()[0]
1078+
result.qfrc_fluid = d.qfrc_fluid.numpy()[0]
10741079
result.qfrc_passive = d.qfrc_passive.numpy()[0]
10751080
result.subtree_linvel = d.subtree_linvel.numpy()[0]
10761081
result.subtree_angmom = d.subtree_angmom.numpy()[0]
10771082
result.qfrc_spring = d.qfrc_spring.numpy()[0]
10781083
result.qfrc_damper = d.qfrc_damper.numpy()[0]
10791084
result.qfrc_gravcomp = d.qfrc_gravcomp.numpy()[0]
1085+
result.qfrc_fluid = d.qfrc_fluid.numpy()[0]
10801086
result.qfrc_actuator = d.qfrc_actuator.numpy()[0]
10811087
result.qfrc_smooth = d.qfrc_smooth.numpy()[0]
10821088
result.qfrc_constraint = d.qfrc_constraint.numpy()[0]

mujoco_warp/_src/io_test.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,20 +222,21 @@ def test_get_data_into_m(self):
222222
np.testing.assert_allclose(mjd.qLD, mjd_ref.qLD)
223223
np.testing.assert_allclose(mjd.qM, mjd_ref.qM)
224224

225-
def test_option_physical_constants(self):
226-
mjm = mujoco.MjModel.from_xml_string("""
225+
def test_ellipsoid_fluid_model(self):
226+
with self.assertRaises(NotImplementedError):
227+
mjm = mujoco.MjModel.from_xml_string(
228+
"""
227229
<mujoco>
228-
<option wind="1 1 1" density="1" viscosity="1"/>
230+
<option density="1"/>
229231
<worldbody>
230-
<body>
231-
<geom type="sphere" size=".1"/>
232+
<body>
233+
<geom type="sphere" size=".1" fluidshape="ellipsoid"/>
232234
<freejoint/>
233235
</body>
234-
</worldbody>
235-
</mujoco>
236-
""")
237-
238-
with self.assertRaises(NotImplementedError):
236+
</worldbody>
237+
</mujoco>
238+
"""
239+
)
239240
mjwarp.put_model(mjm)
240241

241242
def test_jacobian_auto(self):

mujoco_warp/_src/passive.py

Lines changed: 176 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717

1818
from . import math
1919
from . import support
20+
from .types import MJ_MINVAL
2021
from .types import Data
2122
from .types import DisableBit
2223
from .types import JointType
2324
from .types import Model
25+
from .types import Option
2426
from .warp_util import event_scope
2527

2628

@@ -94,25 +96,6 @@ def _spring_passive(
9496
qfrc_spring_out[worldid, dofid] = -stiffness * fdif
9597

9698

97-
@wp.kernel
98-
def _damper_passive(
99-
# Model:
100-
dof_damping: wp.array(dtype=float),
101-
# Data in:
102-
qvel_in: wp.array2d(dtype=float),
103-
qfrc_spring_in: wp.array2d(dtype=float),
104-
# Data out:
105-
qfrc_damper_out: wp.array2d(dtype=float),
106-
qfrc_passive_out: wp.array2d(dtype=float),
107-
):
108-
worldid, dofid = wp.tid()
109-
110-
qfrc_damper = -dof_damping[dofid] * qvel_in[worldid, dofid]
111-
112-
qfrc_damper_out[worldid, dofid] = qfrc_damper
113-
qfrc_passive_out[worldid, dofid] = qfrc_damper + qfrc_spring_in[worldid, dofid]
114-
115-
11699
@wp.kernel
117100
def _gravity_force(
118101
# Model:
@@ -143,50 +126,187 @@ def _gravity_force(
143126

144127

145128
@wp.kernel
146-
def _qfrc_passive_gravcomp(
129+
def _box_fluid(
130+
# Model:
131+
opt_wind: wp.vec3,
132+
opt_density: float,
133+
opt_viscosity: float,
134+
body_rootid: wp.array(dtype=int),
135+
body_mass: wp.array(dtype=float),
136+
body_inertia: wp.array(dtype=wp.vec3),
137+
# Data in:
138+
xipos_in: wp.array2d(dtype=wp.vec3),
139+
ximat_in: wp.array2d(dtype=wp.mat33),
140+
subtree_com_in: wp.array2d(dtype=wp.vec3),
141+
cvel_in: wp.array2d(dtype=wp.spatial_vector),
142+
# Data out:
143+
fluid_applied_out: wp.array2d(dtype=wp.spatial_vector),
144+
):
145+
"""Fluid forces based on inertia-box approximation."""
146+
147+
worldid, bodyid = wp.tid()
148+
149+
# map from CoM-centered to local body-centered 6D velocity
150+
151+
# body-inertial
152+
pos = xipos_in[worldid, bodyid]
153+
rot = ximat_in[worldid, bodyid]
154+
rotT = wp.transpose(rot)
155+
156+
# transform velocity
157+
cvel = cvel_in[worldid, bodyid]
158+
torque = wp.spatial_top(cvel)
159+
force = wp.spatial_bottom(cvel)
160+
subtree_com = subtree_com_in[worldid, body_rootid[bodyid]]
161+
dif = pos - subtree_com
162+
force -= wp.cross(dif, torque)
163+
164+
lvel_torque = rotT @ torque
165+
lvel_force = rotT @ force
166+
167+
if opt_wind[0] or opt_wind[1] or opt_wind[2]:
168+
# subtract translational component from body velocity
169+
lvel_force -= rotT @ opt_wind
170+
171+
lfrc_torque = wp.vec3(0.0)
172+
lfrc_force = wp.vec3(0.0)
173+
174+
viscosity = opt_viscosity > 0.0
175+
density = opt_density > 0.0
176+
177+
if viscosity or density:
178+
inertia = body_inertia[bodyid]
179+
mass = body_mass[bodyid]
180+
scl = 6.0 / mass
181+
box0 = wp.sqrt(wp.max(MJ_MINVAL, inertia[1] + inertia[2] - inertia[0]) * scl)
182+
box1 = wp.sqrt(wp.max(MJ_MINVAL, inertia[0] + inertia[2] - inertia[1]) * scl)
183+
box2 = wp.sqrt(wp.max(MJ_MINVAL, inertia[0] + inertia[1] - inertia[2]) * scl)
184+
185+
if viscosity:
186+
# diameter of sphere approximation
187+
diam = (box0 + box1 + box2) / 3.0
188+
189+
# angular viscosity
190+
lfrc_torque = -lvel_torque * wp.pow(diam, 3.0) * wp.pi * opt_viscosity
191+
192+
# linear viscosity
193+
lfrc_force = -3.0 * lvel_force * diam * wp.pi * opt_viscosity
194+
195+
if density:
196+
# force
197+
lfrc_force -= wp.vec3(
198+
0.5 * opt_density * box1 * box2 * wp.abs(lvel_force[0]) * lvel_force[0],
199+
0.5 * opt_density * box0 * box2 * wp.abs(lvel_force[1]) * lvel_force[1],
200+
0.5 * opt_density * box0 * box1 * wp.abs(lvel_force[2]) * lvel_force[2],
201+
)
202+
203+
# torque
204+
scl = opt_density / 64.0
205+
box0_pow4 = wp.pow(box0, 4.0)
206+
box1_pow4 = wp.pow(box1, 4.0)
207+
box2_pow4 = wp.pow(box2, 4.0)
208+
lfrc_torque -= wp.vec3(
209+
box0 * (box1_pow4 + box2_pow4) * wp.abs(lvel_torque[0]) * lvel_torque[0] * scl,
210+
box1 * (box0_pow4 + box2_pow4) * wp.abs(lvel_torque[1]) * lvel_torque[1] * scl,
211+
box2 * (box0_pow4 + box1_pow4) * wp.abs(lvel_torque[2]) * lvel_torque[2] * scl,
212+
)
213+
214+
# rotate to global orientation: lfrc -> bfrc
215+
torque = rot @ lfrc_torque
216+
force = rot @ lfrc_force
217+
218+
fluid_applied_out[worldid, bodyid] = wp.spatial_vector(force, torque)
219+
220+
221+
def _fluid(m: Model, d: Data):
222+
wp.launch(
223+
_box_fluid,
224+
dim=(d.nworld, m.nbody),
225+
inputs=[
226+
m.opt.wind,
227+
m.opt.density,
228+
m.opt.viscosity,
229+
m.body_rootid,
230+
m.body_mass,
231+
m.body_inertia,
232+
d.xipos,
233+
d.ximat,
234+
d.subtree_com,
235+
d.cvel,
236+
],
237+
outputs=[
238+
d.fluid_applied,
239+
],
240+
)
241+
242+
# TODO(team): ellipsoid fluid model
243+
244+
support.apply_ft(m, d, d.fluid_applied, d.qfrc_fluid)
245+
246+
247+
@wp.kernel
248+
def _qfrc_passive(
147249
# Model:
148250
jnt_actgravcomp: wp.array(dtype=int),
149251
dof_jntid: wp.array(dtype=int),
252+
dof_damping: wp.array(dtype=float),
150253
# Data in:
254+
qvel_in: wp.array2d(dtype=float),
255+
qfrc_spring_in: wp.array2d(dtype=float),
151256
qfrc_gravcomp_in: wp.array2d(dtype=float),
257+
qfrc_fluid_in: wp.array2d(dtype=float),
258+
# In:
259+
gravcomp: bool,
260+
fluid: bool,
152261
# Data out:
262+
qfrc_damper_out: wp.array2d(dtype=float),
153263
qfrc_passive_out: wp.array2d(dtype=float),
154264
):
155265
worldid, dofid = wp.tid()
156266

267+
# spring
268+
qfrc_passive = qfrc_spring_in[worldid, dofid]
269+
270+
# damper
271+
qfrc_damper = -dof_damping[dofid] * qvel_in[worldid, dofid]
272+
qfrc_damper_out[worldid, dofid] = qfrc_damper
273+
274+
qfrc_passive += qfrc_damper
275+
157276
# add gravcomp unless added by actuators
158-
if jnt_actgravcomp[dof_jntid[dofid]]:
159-
return
277+
if gravcomp and not jnt_actgravcomp[dof_jntid[dofid]]:
278+
qfrc_passive += qfrc_gravcomp_in[worldid, dofid]
160279

161-
qfrc_passive_out[worldid, dofid] += qfrc_gravcomp_in[worldid, dofid]
280+
# add fluid force
281+
if fluid:
282+
qfrc_passive += qfrc_fluid_in[worldid, dofid]
283+
284+
qfrc_passive_out[worldid, dofid] = qfrc_passive
162285

163286

164287
@event_scope
165288
def passive(m: Model, d: Data):
166289
"""Adds all passive forces."""
167290
if m.opt.disableflags & DisableBit.PASSIVE:
168-
d.qfrc_passive.zero_()
291+
d.qfrc_spring.zero_()
292+
d.qfrc_damper.zero_()
169293
d.qfrc_gravcomp.zero_()
294+
d.qfrc_fluid.zero_()
295+
d.qfrc_passive.zero_()
170296
return
171297

172-
# TODO(team): mj_ellipsoidFluidModel
173-
# TODO(team): mj_inertiaBoxFluidModell
174-
175298
d.qfrc_spring.zero_()
176299
wp.launch(
177300
_spring_passive,
178301
dim=(d.nworld, m.njnt),
179302
inputs=[m.qpos_spring, m.jnt_type, m.jnt_qposadr, m.jnt_dofadr, m.jnt_stiffness, d.qpos],
180303
outputs=[d.qfrc_spring],
181304
)
182-
wp.launch(
183-
_damper_passive,
184-
dim=(d.nworld, m.nv),
185-
inputs=[m.dof_damping, d.qvel, d.qfrc_spring],
186-
outputs=[d.qfrc_damper, d.qfrc_passive],
187-
)
188-
if m.ngravcomp and not (m.opt.disableflags & DisableBit.GRAVITY):
189-
d.qfrc_gravcomp.zero_()
305+
306+
gravcomp = m.ngravcomp and not (m.opt.disableflags & DisableBit.GRAVITY)
307+
d.qfrc_gravcomp.zero_()
308+
309+
if gravcomp:
190310
wp.launch(
191311
_gravity_force,
192312
dim=(d.nworld, m.nbody - 1, m.nv),
@@ -203,9 +323,24 @@ def passive(m: Model, d: Data):
203323
],
204324
outputs=[d.qfrc_gravcomp],
205325
)
206-
wp.launch(
207-
_qfrc_passive_gravcomp,
208-
dim=(d.nworld, m.nv),
209-
inputs=[m.jnt_actgravcomp, m.dof_jntid, d.qfrc_gravcomp],
210-
outputs=[d.qfrc_passive],
211-
)
326+
327+
fluid = m.opt.density or m.opt.viscosity or m.opt.wind[0] or m.opt.wind[1] or m.opt.wind[2]
328+
if fluid:
329+
_fluid(m, d)
330+
331+
wp.launch(
332+
_qfrc_passive,
333+
dim=(d.nworld, m.nv),
334+
inputs=[
335+
m.jnt_actgravcomp,
336+
m.dof_jntid,
337+
m.dof_damping,
338+
d.qvel,
339+
d.qfrc_spring,
340+
d.qfrc_gravcomp,
341+
d.qfrc_fluid,
342+
gravcomp,
343+
fluid,
344+
],
345+
outputs=[d.qfrc_damper, d.qfrc_passive],
346+
)

0 commit comments

Comments
 (0)