Import google-deepmind/mujoco_warp from GitHub.

PiperOrigin-RevId: 844675206
Change-Id: I02f75561eac3520d7107708dcece10cc82c106cd
This commit is contained in:
Taylor Howell
2025-12-15 02:29:15 -08:00
committed by Copybara-Service
parent ccbfc6c8bf
commit b64b527e13
26 changed files with 1251 additions and 996 deletions
+9 -5
View File
@@ -16,8 +16,12 @@ jax-cuda12-pjrt==0.5.3; python_version >= '3.10' \
jax-cuda12-pjrt==0.4.30; python_version == '3.9' \ jax-cuda12-pjrt==0.4.30; python_version == '3.9' \
--hash=sha256:895d0198ad99638fcaf976c47592e2a543eef79ea15fabd24a402d055390c328 \ --hash=sha256:895d0198ad99638fcaf976c47592e2a543eef79ea15fabd24a402d055390c328 \
--hash=sha256:c36fb1e0c236563bf3a87e70f4d1ab28a31d7cf5d722c9ede30c4172116e8bcb --hash=sha256:c36fb1e0c236563bf3a87e70f4d1ab28a31d7cf5d722c9ede30c4172116e8bcb
warp-lang==1.10.0 \ warp-lang==1.10.1 \
--hash=sha256:428a6388ba8c9b3ded973226ddb5be59b16e3b3f28ac939a5036ad2d7cdc79ed \ --hash=sha256:0c6f44d4136cfc86316f5d35883a863b4dca2bc318331597e755ea872db0cd97 \
--hash=sha256:1bdff31e170b00c89fb9d8b647e906fefdcdcf8741925126d1c0be42783174fa \ --hash=sha256:2884b642f16b07b930b3605193c1b97d183eef80c5b0083d3a17473aee92138e \
--hash=sha256:4aa8eb63cae5ee0d6dbdbdfc305d124140ec975475c97d4458f412396fb39eab \ --hash=sha256:7c8f839a01042677d1f417d31abe9304f9319fca7041c2ed82937ed511eb2362 \
--hash=sha256:81f73055e76a6a3f2284cf2b5fe542a341156861101df9f291bd5b59925ff6e5 --hash=sha256:7addd14a913b50c406ba3b1abebf3a208d01591a1c382da799cff2806d234c5a \
--hash=sha256:5f5ce0147d48f86ccab66b834aa54c8adedbbb2ba86e45b2b904b182fa963509 \
--hash=sha256:2068e877dd2972d841c1eb63f98eadce326e77ba58f09e8474d0847276f47633 \
--hash=sha256:96d24694333e30eac888b2c2962bc5ca4b2369f56707e4c54a465fa37b7ae007 \
--hash=sha256:b77ffe935e06cb57e76f6d4dd8bae7d59294ce63977ea8a0a4fd8e30d77c0a7f
+1
View File
@@ -56,6 +56,7 @@ from mujoco.mjx.third_party.mujoco_warp._src.smooth import com_pos as com_pos
from mujoco.mjx.third_party.mujoco_warp._src.smooth import com_vel as com_vel from mujoco.mjx.third_party.mujoco_warp._src.smooth import com_vel as com_vel
from mujoco.mjx.third_party.mujoco_warp._src.smooth import crb as crb from mujoco.mjx.third_party.mujoco_warp._src.smooth import crb as crb
from mujoco.mjx.third_party.mujoco_warp._src.smooth import factor_m as factor_m from mujoco.mjx.third_party.mujoco_warp._src.smooth import factor_m as factor_m
from mujoco.mjx.third_party.mujoco_warp._src.smooth import flex as flex
from mujoco.mjx.third_party.mujoco_warp._src.smooth import kinematics as kinematics from mujoco.mjx.third_party.mujoco_warp._src.smooth import kinematics as kinematics
from mujoco.mjx.third_party.mujoco_warp._src.smooth import rne as rne from mujoco.mjx.third_party.mujoco_warp._src.smooth import rne as rne
from mujoco.mjx.third_party.mujoco_warp._src.smooth import rne_postconstraint as rne_postconstraint from mujoco.mjx.third_party.mujoco_warp._src.smooth import rne_postconstraint as rne_postconstraint
+2 -1
View File
@@ -174,6 +174,7 @@ class BenchmarkSuite:
batch_size = -1 batch_size = -1
nconmax = -1 nconmax = -1
njmax = -1 njmax = -1
nstep = 1000
param_names = ("function",) param_names = ("function",)
params = ( params = (
"jit_duration", "jit_duration",
@@ -239,7 +240,7 @@ class BenchmarkSuite:
d = io.put_data(mjm, mjd, self.batch_size, self.nconmax, self.njmax) d = io.put_data(mjm, mjd, self.batch_size, self.nconmax, self.njmax)
free_after = wp.get_device().free_memory free_after = wp.get_device().free_memory
jit_duration, _, trace, _, _, solver_niter, _ = benchmark(forward.step, m, d, 1000, ctrls, True, False, True) jit_duration, _, trace, _, _, solver_niter, _ = benchmark(forward.step, m, d, self.nstep, ctrls, True, False, True)
metrics = { metrics = {
"jit_duration": jit_duration, "jit_duration": jit_duration,
"solver_niter_mean": np.mean(solver_niter), "solver_niter_mean": np.mean(solver_niter),
+58 -140
View File
@@ -23,9 +23,8 @@ def create_blocked_cholesky_func(block_size: int):
@wp.func @wp.func
def blocked_cholesky_func( def blocked_cholesky_func(
# In: # In:
tid_block: int,
A: wp.array(dtype=float, ndim=2), A: wp.array(dtype=float, ndim=2),
active_matrix_size: int, matrix_size: int,
# Out: # Out:
L: wp.array(dtype=float, ndim=2), L: wp.array(dtype=float, ndim=2),
): ):
@@ -33,89 +32,57 @@ def create_blocked_cholesky_func(block_size: int):
It returns a lower-triangular matrix L such that A = L L^T. It returns a lower-triangular matrix L such that A = L L^T.
""" """
num_threads_per_block = wp.block_dim() # TODO(team): remove conditional after mjwarp relies on >= 1.11
bleeding_edge_warp = wp.static(wp.__version__ >= "1.11")
# Round up active_matrix_size to next multiple of block_size
n = ((active_matrix_size + block_size - 1) // block_size) * block_size
# Process the matrix in blocks along its leading dimension. # Process the matrix in blocks along its leading dimension.
for k in range(0, n, block_size): for k in range(0, matrix_size, block_size):
end = k + block_size end = k + block_size
# Load current diagonal block A[k:end, k:end] # Load current diagonal block A[k:end, k:end]
# and update with contributions from previously computed blocks. # and update with contributions from previously computed blocks.
A_kk_tile = wp.tile_load(A, shape=(block_size, block_size), offset=(k, k), storage="shared") A_kk_tile = wp.tile_load(A, shape=(block_size, block_size), offset=(k, k), storage="shared")
# The following if pads the matrix if it is not divisible by block_size
if k + block_size > active_matrix_size or k + block_size > active_matrix_size:
num_tile_elements = block_size * block_size
num_iterations = (num_tile_elements + num_threads_per_block - 1) // num_threads_per_block
for i in range(num_iterations): for j in range(0, k, block_size):
linear_index = tid_block + i * num_threads_per_block L_block = wp.tile_load(L, shape=(block_size, block_size), offset=(k, j), storage="shared")
linear_index = linear_index % num_tile_elements if bleeding_edge_warp:
row = linear_index // block_size wp.tile_matmul(L_block, wp.tile_transpose(L_block), A_kk_tile, alpha=-1.0)
col = linear_index % block_size else:
value = A_kk_tile[row, col] A_kk_tile -= wp.tile_matmul(L_block, wp.tile_transpose(L_block))
if k + row >= active_matrix_size or k + col >= active_matrix_size:
value = wp.where(row == col, float(1), float(0))
A_kk_tile[row, col] = value
if k > 0:
for j in range(0, k, block_size):
L_block = wp.tile_load(L, shape=(block_size, block_size), offset=(k, j))
L_block_T = wp.tile_transpose(L_block)
L_L_T_block = wp.tile_matmul(L_block, L_block_T)
A_kk_tile -= L_L_T_block
# Compute the Cholesky factorization for the block # Compute the Cholesky factorization for the block
L_kk_tile = wp.tile_cholesky(A_kk_tile) L_kk_tile = wp.tile_cholesky(A_kk_tile)
wp.tile_store(L, L_kk_tile, offset=(k, k)) wp.tile_store(L, L_kk_tile, offset=(k, k))
# Process the blocks below the current block # Process the blocks below the current block
for i in range(end, n, block_size): for i in range(end, matrix_size, block_size):
A_ik_tile = wp.tile_load(A, shape=(block_size, block_size), offset=(i, k), storage="shared") A_ik_tile = wp.tile_load(A, shape=(block_size, block_size), offset=(i, k), storage="shared")
# The following if pads the matrix if it is not divisible by block_size
if i + block_size > active_matrix_size or k + block_size > active_matrix_size:
num_tile_elements = block_size * block_size
num_iterations = (num_tile_elements + num_threads_per_block - 1) // num_threads_per_block
for ii in range(num_iterations): for j in range(0, k, block_size):
linear_index = tid_block + ii * num_threads_per_block L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(i, j), storage="shared")
linear_index = linear_index % num_tile_elements L_2_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(k, j), storage="shared")
row = linear_index // block_size if bleeding_edge_warp:
col = linear_index % block_size wp.tile_matmul(L_tile, wp.tile_transpose(L_2_tile), A_ik_tile, alpha=-1.0)
value = A_ik_tile[row, col] else:
if i + row >= active_matrix_size or k + col >= active_matrix_size: A_ik_tile -= wp.tile_matmul(L_tile, wp.tile_transpose(L_2_tile))
value = wp.where(i + row == k + col, float(1), float(0))
A_ik_tile[row, col] = value
if k > 0: if bleeding_edge_warp:
for j in range(0, k, block_size): wp.tile_lower_solve_inplace(L_kk_tile, wp.tile_transpose(A_ik_tile))
L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(i, j)) else:
L_2_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(k, j)) A_ik_tile = wp.tile_transpose(wp.tile_lower_solve(L_kk_tile, wp.tile_transpose(A_ik_tile)))
L_T_tile = wp.tile_transpose(L_2_tile) wp.tile_store(L, A_ik_tile, offset=(i, k))
L_L_T_tile = wp.tile_matmul(L_tile, L_T_tile)
A_ik_tile -= L_L_T_tile
t = wp.tile_transpose(A_ik_tile)
tmp = wp.tile_lower_solve(L_kk_tile, t)
sol_tile = wp.tile_transpose(tmp)
wp.tile_store(L, sol_tile, offset=(i, k))
return blocked_cholesky_func return blocked_cholesky_func
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def create_blocked_cholesky_solve_func(block_size: int): def create_blocked_cholesky_solve_func(block_size: int, matrix_size_static: int):
@wp.func @wp.func
def blocked_cholesky_solve_func( def blocked_cholesky_solve_func(
# In: # In:
tid_block: int,
L: wp.array(dtype=float, ndim=2), L: wp.array(dtype=float, ndim=2),
b: wp.array(dtype=float, ndim=2), b: wp.array(dtype=float, ndim=2),
tmp: wp.array(dtype=float, ndim=2), matrix_size: int,
active_matrix_size: int,
# Out: # Out:
x: wp.array(dtype=float, ndim=2), x: wp.array(dtype=float, ndim=2),
): ):
@@ -124,94 +91,45 @@ def create_blocked_cholesky_solve_func(block_size: int):
Solves A x = b given the Cholesky factor L (A = L L^T) using blocked forward and backward Solves A x = b given the Cholesky factor L (A = L L^T) using blocked forward and backward
substitution. substitution.
""" """
num_threads_per_block = wp.block_dim() # TODO(team): remove conditional after mjwarp relies on >= 1.11
bleeding_edge_warp = wp.static(wp.__version__ >= "1.11")
# Round up active_matrix_size to next multiple of block_size rhs_tile = wp.tile_load(b, shape=(matrix_size_static, 1), offset=(0, 0), storage="shared", bounds_check=False)
n = ((active_matrix_size + block_size - 1) // block_size) * block_size
# Forward substitution: solve L y = b # Forward substitution: solve L y = b
for i in range(0, n, block_size): for i in range(0, matrix_size, block_size):
i_end = i + block_size rhs_view = wp.tile_view(rhs_tile, shape=(block_size, 1), offset=(i, 0))
rhs_tile = wp.tile_load(b, shape=(block_size, 1), offset=(i, 0)) for j in range(0, i, block_size):
if i > 0: L_block = wp.tile_load(L, shape=(block_size, block_size), offset=(i, j), storage="shared")
for j in range(0, i, block_size): y_block = wp.tile_view(rhs_tile, shape=(block_size, 1), offset=(j, 0))
L_block = wp.tile_load(L, shape=(block_size, block_size), offset=(i, j)) if bleeding_edge_warp:
y_block = wp.tile_load(tmp, shape=(block_size, 1), offset=(j, 0)) wp.tile_matmul(L_block, y_block, rhs_view, alpha=-1.0)
Ly_block = wp.tile_matmul(L_block, y_block) else:
rhs_tile -= Ly_block rhs_view -= wp.tile_matmul(L_block, y_block)
L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(i, i))
# The following if pads the matrix if it is not divisible by block_size L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(i, i), storage="shared")
if i + block_size > active_matrix_size: if bleeding_edge_warp:
num_tile_elements = block_size * block_size wp.tile_lower_solve_inplace(L_tile, rhs_view)
num_iterations = (num_tile_elements + num_threads_per_block - 1) // num_threads_per_block else:
rhs_tmp = wp.tile_lower_solve(L_tile, rhs_view)
for ii in range(num_iterations): wp.tile_assign(rhs_tile, rhs_tmp, offset=(i, 0))
linear_index = tid_block + ii * num_threads_per_block
linear_index = linear_index % num_tile_elements
row = linear_index // block_size
col = linear_index % block_size
value = L_tile[row, col]
if i + row >= active_matrix_size or i + col >= active_matrix_size:
value = wp.where(row == col, float(1), float(0))
L_tile[row, col] = value
# Handle rhs
num_tile_elements = block_size
num_iterations = (num_tile_elements + num_threads_per_block - 1) // num_threads_per_block
for ii in range(num_iterations):
linear_index = tid_block + ii * num_threads_per_block
linear_index = linear_index % num_tile_elements
value = rhs_tile[linear_index, 0]
if i + linear_index >= active_matrix_size:
value = float(0)
rhs_tile[linear_index, 0] = value
y_tile = wp.tile_lower_solve(L_tile, rhs_tile)
wp.tile_store(tmp, y_tile, offset=(i, 0))
# Backward substitution: solve L^T x = y # Backward substitution: solve L^T x = y
for i in range(n - block_size, -1, -block_size): for i in range(matrix_size - block_size, -1, -block_size):
i_end = i + block_size i_end = i + block_size
rhs_tile = wp.tile_load(tmp, shape=(block_size, 1), offset=(i, 0)) tmp_tile = wp.tile_view(rhs_tile, shape=(block_size, 1), offset=(i, 0))
if i_end < n: for j in range(i_end, matrix_size, block_size):
for j in range(i_end, n, block_size): L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(j, i), storage="shared")
L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(j, i)) x_tile = wp.tile_load(x, shape=(block_size, 1), offset=(j, 0), storage="shared", bounds_check=False)
L_T_tile = wp.tile_transpose(L_tile) if bleeding_edge_warp:
x_tile = wp.tile_load(x, shape=(block_size, 1), offset=(j, 0)) wp.tile_matmul(wp.tile_transpose(L_tile), x_tile, tmp_tile, alpha=-1.0)
L_T_x_tile = wp.tile_matmul(L_T_tile, x_tile) else:
rhs_tile -= L_T_x_tile tmp_tile -= wp.tile_matmul(wp.tile_transpose(L_tile), x_tile)
L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(i, i)) L_tile = wp.tile_load(L, shape=(block_size, block_size), offset=(i, i), storage="shared")
# The following if pads the matrix if it is not divisible by block_size if bleeding_edge_warp:
if i + block_size > active_matrix_size: wp.tile_upper_solve_inplace(wp.tile_transpose(L_tile), tmp_tile)
num_tile_elements = block_size * block_size else:
num_iterations = (num_tile_elements + num_threads_per_block - 1) // num_threads_per_block tmp_tile = wp.tile_upper_solve(wp.tile_transpose(L_tile), tmp_tile)
wp.tile_store(x, tmp_tile, offset=(i, 0), bounds_check=False)
for ii in range(num_iterations):
linear_index = tid_block + ii * num_threads_per_block
linear_index = linear_index % num_tile_elements
row = linear_index // block_size
col = linear_index % block_size
value = L_tile[row, col]
if i + row >= active_matrix_size or i + col >= active_matrix_size:
value = wp.where(row == col, float(1), float(0))
L_tile[row, col] = value
# Handle rhs
num_tile_elements = block_size
num_iterations = (num_tile_elements + num_threads_per_block - 1) // num_threads_per_block
for ii in range(num_iterations):
linear_index = tid_block + ii * num_threads_per_block
linear_index = linear_index % num_tile_elements
value = rhs_tile[linear_index, 0]
if i + linear_index >= active_matrix_size:
value = float(0)
rhs_tile[linear_index, 0] = value
x_tile = wp.tile_upper_solve(wp.tile_transpose(L_tile), rhs_tile)
wp.tile_store(x, x_tile, offset=(i, 0))
return blocked_cholesky_solve_func return blocked_cholesky_solve_func
@@ -195,8 +195,8 @@ def _hfield_filter(
def ccd_kernel_builder( def ccd_kernel_builder(
geomtype1: int, geomtype1: int,
geomtype2: int, geomtype2: int,
ccd_iterations: int, gjk_iterations: int,
is_hfield: bool, epa_iterations: int,
use_multiccd: bool, use_multiccd: bool,
): ):
@wp.func @wp.func
@@ -274,7 +274,8 @@ def ccd_kernel_builder(
dist, ncontact, w1, w2, idx = ccd( dist, ncontact, w1, w2, idx = ccd(
opt_ccd_tolerance[worldid % opt_ccd_tolerance.shape[0]], opt_ccd_tolerance[worldid % opt_ccd_tolerance.shape[0]],
cutoff, cutoff,
ccd_iterations, gjk_iterations,
epa_iterations,
geom1, geom1,
geom2, geom2,
geomtype1, geomtype1,
@@ -484,7 +485,7 @@ def ccd_kernel_builder(
worldid = collision_worldid_in[tid] worldid = collision_worldid_in[tid]
# height field filter # height field filter
if wp.static(is_hfield): if wp.static(geomtype1 == GeomType.HFIELD.value):
no_hf_collision, xmin, xmax, ymin, ymax, zmin, zmax = _hfield_filter( no_hf_collision, xmin, xmax, ymin, ymax, zmin, zmax = _hfield_filter(
geom_dataid, geom_aabb, geom_rbound, geom_margin, hfield_size, geom_xpos_in, geom_xmat_in, worldid, g1, g2 geom_dataid, geom_aabb, geom_rbound, geom_margin, hfield_size, geom_xpos_in, geom_xmat_in, worldid, g1, g2
) )
@@ -538,7 +539,7 @@ def ccd_kernel_builder(
) )
# see MuJoCo mjc_ConvexHField # see MuJoCo mjc_ConvexHField
if wp.static(is_hfield): if wp.static(geomtype1 == GeomType.HFIELD.value):
geom1_dataid = geom_dataid[g1] geom1_dataid = geom_dataid[g1]
# height field subgrid # height field subgrid
@@ -645,7 +646,8 @@ def ccd_kernel_builder(
dist, ncontact, w1, w2, idx = ccd( dist, ncontact, w1, w2, idx = ccd(
opt_ccd_tolerance[worldid % opt_ccd_tolerance.shape[0]], opt_ccd_tolerance[worldid % opt_ccd_tolerance.shape[0]],
0.0, 0.0,
ccd_iterations, gjk_iterations,
epa_iterations,
geom1, geom1,
geom2, geom2,
geomtype1, geomtype1,
@@ -967,34 +969,41 @@ def convex_narrowphase(m: Model, d: Data):
kernel for each type of convex collision pair present in the model, avoiding unnecessary kernel for each type of convex collision pair present in the model, avoiding unnecessary
computations for non-existent pair types. computations for non-existent pair types.
""" """
if not any(m.geom_pair_type_count[upper_trid_index(len(GeomType), g[0].value, g[1].value)] for g in _CONVEX_COLLISION_PAIRS):
def _pair_count(p1: int, p2: int) -> int:
return m.geom_pair_type_count[upper_trid_index(len(GeomType), p1, p2)]
# no convex collisions, early return
if not any(_pair_count(g[0].value, g[1].value) for g in _CONVEX_COLLISION_PAIRS):
return return
epa_iterations = m.opt.ccd_iterations
# set to true to enable multiccd # set to true to enable multiccd
use_multiccd = False use_multiccd = False
nmaxpolygon = m.nmaxpolygon if use_multiccd else 0 nmaxpolygon = m.nmaxpolygon if use_multiccd else 0
nmaxmeshdeg = m.nmaxmeshdeg if use_multiccd else 0 nmaxmeshdeg = m.nmaxmeshdeg if use_multiccd else 0
# epa_vert: vertices in EPA polytope in Minkowski space # epa_vert: vertices in EPA polytope in Minkowski space
epa_vert = wp.empty(shape=(d.naconmax, 5 + m.opt.ccd_iterations), dtype=wp.vec3) epa_vert = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=wp.vec3)
# epa_vert1: vertices in EPA polytope in geom 1 space # epa_vert1: vertices in EPA polytope in geom 1 space
epa_vert1 = wp.empty(shape=(d.naconmax, 5 + m.opt.ccd_iterations), dtype=wp.vec3) epa_vert1 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=wp.vec3)
# epa_vert2: vertices in EPA polytope in geom 2 space # epa_vert2: vertices in EPA polytope in geom 2 space
epa_vert2 = wp.empty(shape=(d.naconmax, 5 + m.opt.ccd_iterations), dtype=wp.vec3) epa_vert2 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=wp.vec3)
# epa_vert_index1: vertex indices in EPA polytope for geom 1 # epa_vert_index1: vertex indices in EPA polytope for geom 1
epa_vert_index1 = wp.empty(shape=(d.naconmax, 5 + m.opt.ccd_iterations), dtype=int) epa_vert_index1 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=int)
# epa_vert_index2: vertex indices in EPA polytope for geom 2 (naconmax, 5 + CCDiter) # epa_vert_index2: vertex indices in EPA polytope for geom 2 (naconmax, 5 + CCDiter)
epa_vert_index2 = wp.empty(shape=(d.naconmax, 5 + m.opt.ccd_iterations), dtype=int) epa_vert_index2 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=int)
# epa_face: faces of polytope represented by three indices # epa_face: faces of polytope represented by three indices
epa_face = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * m.opt.ccd_iterations), dtype=wp.vec3i) epa_face = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=wp.vec3i)
# epa_pr: projection of origin on polytope faces # epa_pr: projection of origin on polytope faces
epa_pr = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * m.opt.ccd_iterations), dtype=wp.vec3) epa_pr = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=wp.vec3)
# epa_norm2: epa_pr * epa_pr # epa_norm2: epa_pr * epa_pr
epa_norm2 = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * m.opt.ccd_iterations), dtype=float) epa_norm2 = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=float)
# epa_index: index of face in polytope map # epa_index: index of face in polytope map
epa_index = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * m.opt.ccd_iterations), dtype=int) epa_index = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=int)
# epa_map: status of faces in polytope # epa_map: status of faces in polytope
epa_map = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * m.opt.ccd_iterations), dtype=int) epa_map = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=int)
# epa_horizon: index pair (i j) of edges on horizon # epa_horizon: index pair (i j) of edges on horizon
epa_horizon = wp.empty(shape=(d.naconmax, 2 * MJ_MAX_EPAHORIZON), dtype=int) epa_horizon = wp.empty(shape=(d.naconmax, 2 * MJ_MAX_EPAHORIZON), dtype=int)
# multiccd_polygon: clipped contact surface # multiccd_polygon: clipped contact surface
@@ -1023,9 +1032,9 @@ def convex_narrowphase(m: Model, d: Data):
for geom_pair in _CONVEX_COLLISION_PAIRS: for geom_pair in _CONVEX_COLLISION_PAIRS:
g1 = geom_pair[0].value g1 = geom_pair[0].value
g2 = geom_pair[1].value g2 = geom_pair[1].value
if m.geom_pair_type_count[upper_trid_index(len(GeomType), g1, g2)]: if _pair_count(g1, g2):
wp.launch( wp.launch(
ccd_kernel_builder(g1, g2, m.opt.ccd_iterations, g1 == GeomType.HFIELD, use_multiccd), ccd_kernel_builder(g1, g2, m.opt.ccd_iterations, epa_iterations, use_multiccd),
dim=d.naconmax, dim=d.naconmax,
inputs=[ inputs=[
m.opt.ccd_tolerance, m.opt.ccd_tolerance,
+126 -88
View File
@@ -18,7 +18,6 @@ from typing import Tuple
import warp as wp import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src.collision_primitive import Geom from mujoco.mjx.third_party.mujoco_warp._src.collision_primitive import Geom
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
from mujoco.mjx.third_party.mujoco_warp._src.types import mat43 from mujoco.mjx.third_party.mujoco_warp._src.types import mat43
from mujoco.mjx.third_party.mujoco_warp._src.types import mat63 from mujoco.mjx.third_party.mujoco_warp._src.types import mat63
@@ -28,7 +27,8 @@ wp.set_module_options({"enable_backward": False})
FLOAT_MIN = -1e30 FLOAT_MIN = -1e30
FLOAT_MAX = 1e30 FLOAT_MAX = 1e30
MJ_MINVAL2 = MJ_MINVAL * MJ_MINVAL MINVAL = 1e-15
MIN_DIST = 1e-10
# TODO(kbayes): write out formulas to derive these constants # TODO(kbayes): write out formulas to derive these constants
FACE_TOL = 0.99999872 FACE_TOL = 0.99999872
@@ -122,7 +122,7 @@ def _support(geom: Geom, geomtype: int, dir: wp.vec3) -> SupportPoint:
res = wp.vec3(0.0, 0.0, 0.0) res = wp.vec3(0.0, 0.0, 0.0)
# set result in XY plane: support on circle # set result in XY plane: support on circle
d = wp.sqrt(local_dir[0] * local_dir[0] + local_dir[1] * local_dir[1]) d = wp.sqrt(local_dir[0] * local_dir[0] + local_dir[1] * local_dir[1])
if d > MJ_MINVAL: if d > MINVAL:
scl = geom.size[0] / d scl = geom.size[0] / d
res[0] = local_dir[0] * scl res[0] = local_dir[0] * scl
res[1] = local_dir[1] * scl res[1] = local_dir[1] * scl
@@ -173,6 +173,8 @@ def _support(geom: Geom, geomtype: int, dir: wp.vec3) -> SupportPoint:
sp.point = geom.rot @ sp.point + geom.pos sp.point = geom.rot @ sp.point + geom.pos
elif geomtype == GeomType.HFIELD: elif geomtype == GeomType.HFIELD:
max_dist = float(FLOAT_MIN) max_dist = float(FLOAT_MIN)
# TODO(kbayes): Support edge prisms
sp.vertex_index = wp.where(local_dir[2] < 0, -2, -3)
for i in range(6): for i in range(6):
vert = geom.hfprism[i] vert = geom.hfprism[i]
dist = wp.dot(vert, local_dir) dist = wp.dot(vert, local_dir)
@@ -241,7 +243,7 @@ def _linear_combine(n: int, coefs: wp.vec4, mat: mat43) -> wp.vec3:
@wp.func @wp.func
def _almost_equal(v1: wp.vec3, v2: wp.vec3) -> bool: def _almost_equal(v1: wp.vec3, v2: wp.vec3) -> bool:
return wp.abs(v1[0] - v2[0]) < MJ_MINVAL and wp.abs(v1[1] - v2[1]) < MJ_MINVAL and wp.abs(v1[2] - v2[2]) < MJ_MINVAL return wp.abs(v1[0] - v2[0]) < MINVAL and wp.abs(v1[1] - v2[1]) < MINVAL and wp.abs(v1[2] - v2[2]) < MINVAL
@wp.func @wp.func
@@ -291,7 +293,7 @@ def _project_origin_plane(v1: wp.vec3, v2: wp.vec3, v3: wp.vec3) -> Tuple[wp.vec
nn = wp.dot(n, n) nn = wp.dot(n, n)
if nn == 0: if nn == 0:
return z, 1 return z, 1
if nv != 0 and nn > MJ_MINVAL: if nv != 0 and nn > MINVAL:
v = (nv / nn) * n v = (nv / nn) * n
return v, 0 return v, 0
@@ -301,7 +303,7 @@ def _project_origin_plane(v1: wp.vec3, v2: wp.vec3, v3: wp.vec3) -> Tuple[wp.vec
nn = wp.dot(n, n) nn = wp.dot(n, n)
if nn == 0: if nn == 0:
return z, 1 return z, 1
if nv != 0 and nn > MJ_MINVAL: if nv != 0 and nn > MINVAL:
v = (nv / nn) * n v = (nv / nn) * n
return v, 0 return v, 0
@@ -579,13 +581,14 @@ def gjk(
simplex_index1 = wp.vec4i() simplex_index1 = wp.vec4i()
simplex_index2 = wp.vec4i() simplex_index2 = wp.vec4i()
n = int(0) n = int(0)
cnt = int(1)
coordinates = wp.vec4() # barycentric coordinates coordinates = wp.vec4() # barycentric coordinates
epsilon = wp.where(is_discrete, 0.0, 0.5 * tolerance * tolerance) epsilon = wp.where(is_discrete, 0.0, 0.5 * tolerance * tolerance)
# set initial guess # set initial guess
x_k = x1_0 - x2_0 x_k = x1_0 - x2_0
for k in range(gjk_iterations): for _ in range(gjk_iterations):
xnorm = wp.dot(x_k, x_k) xnorm = wp.dot(x_k, x_k)
# TODO(kbayes): determine new constant here # TODO(kbayes): determine new constant here
if xnorm < 1e-12: if xnorm < 1e-12:
@@ -663,6 +666,11 @@ def gjk(
if n == 4: if n == 4:
break break
cnt += 1
if cnt == gjk_iterations:
wp.printf("Warning: opt.ccd_iterations, currently set to %d, needs to be increased.\n", gjk_iterations)
result = GJKResult() result = GJKResult()
# compute the approximate witness points # compute the approximate witness points
@@ -751,7 +759,7 @@ def _tri_point_intersect(v1: wp.vec3, v2: wp.vec3, v3: wp.vec3, p: wp.vec3) -> b
pr[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3 pr[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3
pr[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3 pr[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3
pr[2] = v1[2] * l1 + v2[2] * l2 + v3[2] * l3 pr[2] = v1[2] * l1 + v2[2] * l2 + v3[2] * l3
return wp.norm_l2(pr - p) < MJ_MINVAL return wp.norm_l2(pr - p) < MINVAL
@wp.func @wp.func
@@ -867,36 +875,66 @@ def _delete_face(pt: Polytope, face_id: int) -> int:
@wp.func @wp.func
def _epa_witness(pt: Polytope, face_idx: int) -> Tuple[wp.vec3, wp.vec3]: def _epa_witness(
pt: Polytope, geom1: Geom, geom2: Geom, geomtype1: int, geomtype2: int, face_idx: int
) -> Tuple[wp.vec3, wp.vec3, float]:
face = pt.face[face_idx]
# compute affine coordinates for witness points on plane defined by face # compute affine coordinates for witness points on plane defined by face
v1 = pt.vert[pt.face[face_idx][0]] v1 = pt.vert[face[0]]
v2 = pt.vert[pt.face[face_idx][1]] v2 = pt.vert[face[1]]
v3 = pt.vert[pt.face[face_idx][2]] v3 = pt.vert[face[2]]
coordinates = _tri_affine_coord(v1, v2, v3, pt.face_pr[face_idx]) coordinates = _tri_affine_coord(v1, v2, v3, pt.face_pr[face_idx])
l1 = coordinates[0] l1 = coordinates[0]
l2 = coordinates[1] l2 = coordinates[1]
l3 = coordinates[2] l3 = coordinates[2]
# face on geom 1
v1 = pt.vert1[pt.face[face_idx][0]]
v2 = pt.vert1[pt.face[face_idx][1]]
v3 = pt.vert1[pt.face[face_idx][2]]
x1 = wp.vec3()
x1[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3
x1[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3
x1[2] = v1[2] * l1 + v2[2] * l2 + v3[2] * l3
# face on geom 2 # face on geom 2
v1 = pt.vert2[pt.face[face_idx][0]] v1 = pt.vert2[face[0]]
v2 = pt.vert2[pt.face[face_idx][1]] v2 = pt.vert2[face[1]]
v3 = pt.vert2[pt.face[face_idx][2]] v3 = pt.vert2[face[2]]
x2 = wp.vec3() x2 = wp.vec3()
x2[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3 x2[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3
x2[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3 x2[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3
x2[2] = v1[2] * l1 + v2[2] * l2 + v3[2] * l3 x2[2] = v1[2] * l1 + v2[2] * l2 + v3[2] * l3
return x1, x2 # correct witness points for hfield geoms
i1 = pt.vert_index1[face[0]]
i2 = pt.vert_index1[face[1]]
i3 = pt.vert_index1[face[2]]
if geomtype1 == GeomType.HFIELD and (i1 != i2 or i1 != i3):
# TODO(kbayes): Fix case where geom2 is near bottom of height field or "extreme" prism heights
n = geom1.rot[:, 2]
a = geom1.hfprism[3]
b = geom1.hfprism[4]
c = geom1.hfprism[5]
x2 = wp.normalize(x2)
# TODO(kbayes): Support cases where geom2 is larger than the height field
sp = _support(geom2, geomtype2, x2)
x2 = sp.point
coordinates2 = _tri_affine_coord(a, b, c, x2)
if coordinates2[0] > 0 and coordinates2[1] > 0 and coordinates2[2] > 0:
x1 = coordinates[0] * a + coordinates[1] * b + coordinates[2] * c
else:
p = c
p = wp.where(coordinates[1] > 0, b, p)
p = wp.where(coordinates[0] > 0, a, p)
x1 = x2 - wp.dot(x2 - p, n) * n
return x1, x2, -wp.norm_l2(x1 - x2)
# face on geom 1
v1 = pt.vert1[face[0]]
v2 = pt.vert1[face[1]]
v3 = pt.vert1[face[2]]
x1 = wp.vec3()
x1[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3
x1[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3
x1[2] = v1[2] * l1 + v2[2] * l2 + v3[2] * l3
return x1, x2, -wp.sqrt(pt.face_norm2[face_idx])
@wp.func @wp.func
@@ -926,7 +964,7 @@ def _polytope2(
index = i index = i
# cross product with best coordinate axis # cross product with best coordinate axis
e = wp.vec(0.0, 0.0, 0.0) e = wp.vec3(0.0, 0.0, 0.0)
e[index] = 1.0 e[index] = 1.0
d1 = wp.cross(e, diff) d1 = wp.cross(e, diff)
@@ -956,27 +994,27 @@ def _polytope2(
_epa_support(pt, 4, geom1, geom2, geomtype1, geomtype2, d3 / wp.norm_l2(d3)) _epa_support(pt, 4, geom1, geom2, geomtype1, geomtype2, d3 / wp.norm_l2(d3))
# build hexahedron # build hexahedron
if _attach_face(pt, 0, 0, 2, 3) < MJ_MINVAL: if _attach_face(pt, 0, 0, 2, 3) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 0, 2, 3) return pt, _replace_simplex3(pt, 0, 2, 3)
if _attach_face(pt, 1, 0, 4, 2) < MJ_MINVAL2: if _attach_face(pt, 1, 0, 4, 2) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 0, 4, 2) return pt, _replace_simplex3(pt, 0, 4, 2)
if _attach_face(pt, 2, 0, 3, 4) < MJ_MINVAL2: if _attach_face(pt, 2, 0, 3, 4) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 0, 3, 4) return pt, _replace_simplex3(pt, 0, 3, 4)
if _attach_face(pt, 3, 1, 3, 2) < MJ_MINVAL2: if _attach_face(pt, 3, 1, 3, 2) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 1, 3, 2) return pt, _replace_simplex3(pt, 1, 3, 2)
if _attach_face(pt, 4, 1, 2, 4) < MJ_MINVAL2: if _attach_face(pt, 4, 1, 2, 4) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 1, 2, 4) return pt, _replace_simplex3(pt, 1, 2, 4)
if _attach_face(pt, 5, 1, 4, 3) < MJ_MINVAL2: if _attach_face(pt, 5, 1, 4, 3) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 1, 4, 3) return pt, _replace_simplex3(pt, 1, 4, 3)
@@ -1016,7 +1054,7 @@ def _polytope3(
"""Create polytope for EPA given a 2-simplex from GJK.""" """Create polytope for EPA given a 2-simplex from GJK."""
# get normals in both directions # get normals in both directions
n = wp.cross(simplex[1] - simplex[0], simplex[2] - simplex[0]) n = wp.cross(simplex[1] - simplex[0], simplex[2] - simplex[0])
if wp.norm_l2(n) < MJ_MINVAL: if wp.norm_l2(n) < MINVAL:
pt.status = 2 pt.status = 2
return pt return pt
@@ -1066,22 +1104,22 @@ def _polytope3(
return pt return pt
# create hexahedron for EPA # create hexahedron for EPA
if _attach_face(pt, 0, 4, 0, 1) < MJ_MINVAL2: if _attach_face(pt, 0, 4, 0, 1) < MIN_DIST:
pt.status = 6 pt.status = 6
return pt return pt
if _attach_face(pt, 1, 4, 2, 0) < MJ_MINVAL2: if _attach_face(pt, 1, 4, 2, 0) < MIN_DIST:
pt.status = 7 pt.status = 7
return pt return pt
if _attach_face(pt, 2, 4, 1, 2) < MJ_MINVAL2: if _attach_face(pt, 2, 4, 1, 2) < MIN_DIST:
pt.status = 8 pt.status = 8
return pt return pt
if _attach_face(pt, 3, 3, 1, 0) < MJ_MINVAL2: if _attach_face(pt, 3, 3, 1, 0) < MIN_DIST:
pt.status = 9 pt.status = 9
return pt return pt
if _attach_face(pt, 4, 3, 0, 2) < MJ_MINVAL2: if _attach_face(pt, 4, 3, 0, 2) < MIN_DIST:
pt.status = 10 pt.status = 10
return pt return pt
if _attach_face(pt, 5, 3, 2, 1) < MJ_MINVAL2: if _attach_face(pt, 5, 3, 2, 1) < MIN_DIST:
pt.status = 11 pt.status = 11
return pt return pt
@@ -1140,19 +1178,19 @@ def _polytope4(
pt.vert_index2[3] = simplex_index2[3] pt.vert_index2[3] = simplex_index2[3]
# if the origin is on a face, replace the 3-simplex with a 2-simplex # if the origin is on a face, replace the 3-simplex with a 2-simplex
if _attach_face(pt, 0, 0, 1, 2) < MJ_MINVAL2: if _attach_face(pt, 0, 0, 1, 2) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 0, 1, 2) return pt, _replace_simplex3(pt, 0, 1, 2)
if _attach_face(pt, 1, 0, 3, 1) < MJ_MINVAL2: if _attach_face(pt, 1, 0, 3, 1) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 0, 3, 1) return pt, _replace_simplex3(pt, 0, 3, 1)
if _attach_face(pt, 2, 0, 2, 3) < MJ_MINVAL2: if _attach_face(pt, 2, 0, 2, 3) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 0, 2, 3) return pt, _replace_simplex3(pt, 0, 2, 3)
if _attach_face(pt, 3, 3, 2, 1) < MJ_MINVAL2: if _attach_face(pt, 3, 3, 2, 1) < MIN_DIST:
pt.status = -1 pt.status = -1
return pt, _replace_simplex3(pt, 3, 2, 1) return pt, _replace_simplex3(pt, 3, 2, 1)
@@ -1177,6 +1215,7 @@ def _polytope4(
def _epa( def _epa(
# In: # In:
tolerance: float, tolerance: float,
gjk_iterations: int,
epa_iterations: int, epa_iterations: int,
pt: Polytope, pt: Polytope,
geom1: Geom, geom1: Geom,
@@ -1191,9 +1230,10 @@ def _epa(
idx = int(-1) idx = int(-1)
pidx = int(-1) pidx = int(-1)
epsilon = wp.where(is_discrete, 1e-15, tolerance) epsilon = wp.where(is_discrete, 1e-15, tolerance)
cnt = int(1)
for k in range(epa_iterations): for _ in range(epa_iterations):
pidx = int(idx) pidx = idx
idx = int(-1) idx = int(-1)
# find the face closest to the origin (lower bound for penetration depth) # find the face closest to the origin (lower bound for penetration depth)
@@ -1283,11 +1323,15 @@ def _epa(
# clear horizon # clear horizon
pt.nhorizon = 0 pt.nhorizon = 0
cnt += 1
if cnt == epa_iterations:
wp.printf("Warning: opt.ccd_iterations, currently set to %d, needs to be increased.\n", gjk_iterations)
# return from valid face # return from valid face
if idx > -1: if idx > -1:
x1, x2 = _epa_witness(pt, idx) x1, x2, dist = _epa_witness(pt, geom1, geom2, geomtype1, geomtype2, idx)
return -wp.sqrt(pt.face_norm2[idx]), x1, x2, idx return dist, x1, x2, idx
return 0.0, wp.vec3(), wp.vec3(), -1 return 0.0, wp.vec3(), wp.vec3(), -1
@@ -1696,40 +1740,40 @@ def _box_edge_normals(
def _box_face(mat: wp.mat33, pos: wp.vec3, size: wp.vec3, idx: int, face_out: wp.array(dtype=wp.vec3)) -> int: def _box_face(mat: wp.mat33, pos: wp.vec3, size: wp.vec3, idx: int, face_out: wp.array(dtype=wp.vec3)) -> int:
# compute global coordinates of the box face and face normal # compute global coordinates of the box face and face normal
if idx == 0: # right if idx == 0: # right
face_out[0] = mat @ wp.vec(size[0], size[1], size[2]) + pos face_out[0] = mat @ wp.vec3(size[0], size[1], size[2]) + pos
face_out[1] = mat @ wp.vec(size[0], size[1], -size[2]) + pos face_out[1] = mat @ wp.vec3(size[0], size[1], -size[2]) + pos
face_out[2] = mat @ wp.vec(size[0], -size[1], -size[2]) + pos face_out[2] = mat @ wp.vec3(size[0], -size[1], -size[2]) + pos
face_out[3] = mat @ wp.vec(size[0], -size[1], size[2]) + pos face_out[3] = mat @ wp.vec3(size[0], -size[1], size[2]) + pos
return 4 return 4
if idx == 1: # left if idx == 1: # left
face_out[0] = mat @ wp.vec(-size[0], size[1], -size[2]) + pos face_out[0] = mat @ wp.vec3(-size[0], size[1], -size[2]) + pos
face_out[1] = mat @ wp.vec(-size[0], size[1], size[2]) + pos face_out[1] = mat @ wp.vec3(-size[0], size[1], size[2]) + pos
face_out[2] = mat @ wp.vec(-size[0], -size[1], size[2]) + pos face_out[2] = mat @ wp.vec3(-size[0], -size[1], size[2]) + pos
face_out[3] = mat @ wp.vec(-size[0], -size[1], -size[2]) + pos face_out[3] = mat @ wp.vec3(-size[0], -size[1], -size[2]) + pos
return 4 return 4
if idx == 2: # top if idx == 2: # top
face_out[0] = mat @ wp.vec(-size[0], size[1], -size[2]) + pos face_out[0] = mat @ wp.vec3(-size[0], size[1], -size[2]) + pos
face_out[1] = mat @ wp.vec(size[0], size[1], -size[2]) + pos face_out[1] = mat @ wp.vec3(size[0], size[1], -size[2]) + pos
face_out[2] = mat @ wp.vec(size[0], size[1], size[2]) + pos face_out[2] = mat @ wp.vec3(size[0], size[1], size[2]) + pos
face_out[3] = mat @ wp.vec(-size[0], size[1], size[2]) + pos face_out[3] = mat @ wp.vec3(-size[0], size[1], size[2]) + pos
return 4 return 4
if idx == 3: # bottom if idx == 3: # bottom
face_out[0] = mat @ wp.vec(-size[0], -size[1], size[2]) + pos face_out[0] = mat @ wp.vec3(-size[0], -size[1], size[2]) + pos
face_out[1] = mat @ wp.vec(size[0], -size[1], size[2]) + pos face_out[1] = mat @ wp.vec3(size[0], -size[1], size[2]) + pos
face_out[2] = mat @ wp.vec(size[0], -size[1], -size[2]) + pos face_out[2] = mat @ wp.vec3(size[0], -size[1], -size[2]) + pos
face_out[3] = mat @ wp.vec(-size[0], -size[1], -size[2]) + pos face_out[3] = mat @ wp.vec3(-size[0], -size[1], -size[2]) + pos
return 4 return 4
if idx == 4: # front if idx == 4: # front
face_out[0] = mat @ wp.vec(-size[0], size[1], size[2]) + pos face_out[0] = mat @ wp.vec3(-size[0], size[1], size[2]) + pos
face_out[1] = mat @ wp.vec(size[0], size[1], size[2]) + pos face_out[1] = mat @ wp.vec3(size[0], size[1], size[2]) + pos
face_out[2] = mat @ wp.vec(size[0], -size[1], size[2]) + pos face_out[2] = mat @ wp.vec3(size[0], -size[1], size[2]) + pos
face_out[3] = mat @ wp.vec(-size[0], -size[1], size[2]) + pos face_out[3] = mat @ wp.vec3(-size[0], -size[1], size[2]) + pos
return 4 return 4
if idx == 5: # back if idx == 5: # back
face_out[0] = mat @ wp.vec(size[0], size[1], -size[2]) + pos face_out[0] = mat @ wp.vec3(size[0], size[1], -size[2]) + pos
face_out[1] = mat @ wp.vec(-size[0], size[1], -size[2]) + pos face_out[1] = mat @ wp.vec3(-size[0], size[1], -size[2]) + pos
face_out[2] = mat @ wp.vec(-size[0], -size[1], -size[2]) + pos face_out[2] = mat @ wp.vec3(-size[0], -size[1], -size[2]) + pos
face_out[3] = mat @ wp.vec(size[0], -size[1], -size[2]) + pos face_out[3] = mat @ wp.vec3(size[0], -size[1], -size[2]) + pos
return 4 return 4
return 0 return 0
@@ -1791,7 +1835,6 @@ def _plane_intersect(pn: wp.vec3, pd: float, a: wp.vec3, b: wp.vec3) -> Tuple[fl
@wp.func @wp.func
def _polygon_clip( def _polygon_clip(
# In: # In:
prune: bool,
plane_normal: wp.array(dtype=wp.vec3), plane_normal: wp.array(dtype=wp.vec3),
plane_dist: wp.array(dtype=float), plane_dist: wp.array(dtype=float),
face1: wp.array(dtype=wp.vec3), face1: wp.array(dtype=wp.vec3),
@@ -1871,7 +1914,7 @@ def _polygon_clip(
if npolygon < 1: if npolygon < 1:
return 0, witness1, witness2 return 0, witness1, witness2
if prune and npolygon > 4: if npolygon > 4:
quad = _polygon_quad(polygon_out, npolygon) quad = _polygon_quad(polygon_out, npolygon)
for i in range(4): for i in range(4):
witness2[i] = polygon_out[quad[i]] witness2[i] = polygon_out[quad[i]]
@@ -2102,27 +2145,20 @@ def multicontact(
face2, face2,
) )
# TODO(kbayes): this approximates the contact direction, by scaling the face normal by the
# single contact direction's magnitude. This is effective, but polygonClip should compute
# this for each contact point.
approx_dir = wp.vec3()
# face1 is an edge; clip face1 against face2 # face1 is an edge; clip face1 against face2
if is_edge_contact_geom1: if is_edge_contact_geom1:
approx_dir = wp.norm_l2(dir) * n2[j] approx_dir = wp.norm_l2(dir) * n2[j]
return _polygon_clip(False, plane_normal, plane_dist, face2, nface2, face1, nface1, n2[j], approx_dir, polygon, clipped) return _polygon_clip(plane_normal, plane_dist, face2, nface2, face1, nface1, n2[j], approx_dir, polygon, clipped)
# face2 is an edge; clip face2 against face1 # face2 is an edge; clip face2 against face1
if is_edge_contact_geom2: if is_edge_contact_geom2:
approx_dir = -wp.norm_l2(dir) * n1[j] approx_dir = -wp.norm_l2(dir) * n1[j]
return _polygon_clip(False, plane_normal, plane_dist, face1, nface1, face2, nface2, n1[j], approx_dir, polygon, clipped) return _polygon_clip(plane_normal, plane_dist, face1, nface1, face2, nface2, n1[j], approx_dir, polygon, clipped)
# face-face collision # face-face collision
approx_dir = wp.norm_l2(dir) * n2[j] approx_dir = wp.norm_l2(dir) * n2[j]
# don't prune box-box collisions (expect up to 8 contacts) return _polygon_clip(plane_normal, plane_dist, face1, nface1, face2, nface2, n1[i], approx_dir, polygon, clipped)
prune = not (geomtype1 == GeomType.BOX and geomtype2 == GeomType.BOX)
return _polygon_clip(prune, plane_normal, plane_dist, face1, nface1, face2, nface2, n1[i], approx_dir, polygon, clipped)
@wp.func @wp.func
@@ -2142,7 +2178,8 @@ def ccd(
# In: # In:
tolerance: float, tolerance: float,
cutoff: float, cutoff: float,
ccd_iterations: int, gjk_iterations: int,
epa_iterations: int,
geom1: Geom, geom1: Geom,
geom2: Geom, geom2: Geom,
geomtype1: int, geomtype1: int,
@@ -2176,7 +2213,8 @@ def ccd(
geom1.margin = 0.0 geom1.margin = 0.0
geom1.size = wp.vec3(0.0, geom1.size[1], geom1.size[2]) geom1.size = wp.vec3(0.0, geom1.size[1], geom1.size[2])
if geomtype2 == GeomType.SPHERE or geomtype2 == GeomType.CAPSULE: # TODO(kbayes): support gjk margin trick with height fields
if geomtype1 != GeomType.HFIELD and (geomtype2 == GeomType.SPHERE or geomtype2 == GeomType.CAPSULE):
size2 = geom2.size[0] size2 = geom2.size[0]
full_margin2 = size2 + 0.5 * geom2.margin full_margin2 = size2 + 0.5 * geom2.margin
geom2.margin = 0.0 geom2.margin = 0.0
@@ -2185,7 +2223,7 @@ def ccd(
# special handling for sphere and capsule (shrink to point and line respectively) # special handling for sphere and capsule (shrink to point and line respectively)
if size1 + size2 > 0.0: if size1 + size2 > 0.0:
cutoff += full_margin1 + full_margin2 cutoff += full_margin1 + full_margin2
result = gjk(tolerance, ccd_iterations, geom1, geom2, x_1, x_2, geomtype1, geomtype2, cutoff, is_discrete) result = gjk(tolerance, gjk_iterations, geom1, geom2, x_1, x_2, geomtype1, geomtype2, cutoff, is_discrete)
# shallow penetration, inflate contact # shallow penetration, inflate contact
if result.dist > tolerance: if result.dist > tolerance:
@@ -2201,7 +2239,7 @@ def ccd(
geom2.size = wp.vec3(size2, geom2.size[1], geom2.size[2]) geom2.size = wp.vec3(size2, geom2.size[1], geom2.size[2])
cutoff -= full_margin1 + full_margin2 cutoff -= full_margin1 + full_margin2
result = gjk(tolerance, ccd_iterations, geom1, geom2, x_1, x_2, geomtype1, geomtype2, cutoff, is_discrete) result = gjk(tolerance, gjk_iterations, geom1, geom2, x_1, x_2, geomtype1, geomtype2, cutoff, is_discrete)
# no penetration depth to recover # no penetration depth to recover
if result.dist > tolerance or result.dim < 2: if result.dist > tolerance or result.dim < 2:
@@ -2287,7 +2325,7 @@ def ccd(
if pt.status: if pt.status:
return result.dist, 1, result.x1, result.x2, -1 return result.dist, 1, result.x1, result.x2, -1
dist, x1, x2, idx = _epa(tolerance, ccd_iterations, pt, geom1, geom2, geomtype1, geomtype2, is_discrete) dist, x1, x2, idx = _epa(tolerance, gjk_iterations, epa_iterations, pt, geom1, geom2, geomtype1, geomtype2, is_discrete)
if idx == -1: if idx == -1:
return FLOAT_MAX, 0, wp.vec3(), wp.vec3(), -1 return FLOAT_MAX, 0, wp.vec3(), wp.vec3(), -1
return dist, 1, x1, x2, idx return dist, 1, x1, x2, idx
@@ -1035,7 +1035,7 @@ def plane_box_wrapper(
dist, pos, normal = plane_box(plane.normal, plane.pos, box.pos, box.rot, box.size) dist, pos, normal = plane_box(plane.normal, plane.pos, box.pos, box.rot, box.size)
frame = make_frame(normal) frame = make_frame(normal)
for i in range(4): for i in range(8):
write_contact( write_contact(
naconmax_in, naconmax_in,
i, i,
@@ -1562,16 +1562,9 @@ assert _check_primitive_collisions(), "_PRIMITIVE_COLLISIONS is in invalid order
@cache_kernel @cache_kernel
def _create_narrowphase_kernel(primitive_collisions_types, primitive_collisions_func): def _primitive_narrowphase(primitive_collisions_types, primitive_collisions_func):
# AD: no unique here: @nested_kernel(module="unique", enable_backward=False)
# * we expect this generator to be called only once per model, so no repeated compilation def primitive_narrowphase(
# * module="unique" is generating problems because it uses the function name as the key
# that in turn will cause multiple kernels to be generated with the same name
# this is mostly problematic in cases like the UTs where we don't clear the kernel cache
# between different tests.
@nested_kernel(enable_backward=False)
def _primitive_narrowphase(
# Model: # Model:
geom_type: wp.array(dtype=int), geom_type: wp.array(dtype=int),
geom_condim: wp.array(dtype=int), geom_condim: wp.array(dtype=int),
@@ -1719,20 +1712,11 @@ def _create_narrowphase_kernel(primitive_collisions_types, primitive_collisions_
nacon_out, nacon_out,
) )
return _primitive_narrowphase return primitive_narrowphase
def _primitive_narrowphase_builder(m: Model): _PRIMITIVE_COLLISION_TYPES = []
_primitive_collisions_types = [] _PRIMITIVE_COLLISION_FUNC = []
_primitive_collisions_func = []
for types, func in _PRIMITIVE_COLLISIONS.items():
idx = upper_trid_index(len(GeomType), types[0].value, types[1].value)
if m.geom_pair_type_count[idx] and types not in _primitive_collisions_types:
_primitive_collisions_types.append(types)
_primitive_collisions_func.append(func)
return _create_narrowphase_kernel(_primitive_collisions_types, _primitive_collisions_func)
@event_scope @event_scope
@@ -1751,10 +1735,17 @@ def primitive_narrowphase(m: Model, d: Data):
the specific primitive collision types present in the model, avoiding the specific primitive collision types present in the model, avoiding
unnecessary checks for non-existent collision pairs. unnecessary checks for non-existent collision pairs.
""" """
# we need to figure out how to keep the overhead of this small - not launching anything # TODO(team): keep the overhead of this small - not launching anything
# for pair types without collisions, as well as updating the launch dimensions. # for pair types without collisions, as well as updating the launch dimensions.
for types, func in _PRIMITIVE_COLLISIONS.items():
idx = upper_trid_index(len(GeomType), types[0].value, types[1].value)
if m.geom_pair_type_count[idx] and types not in _PRIMITIVE_COLLISION_TYPES:
_PRIMITIVE_COLLISION_TYPES.append(types)
_PRIMITIVE_COLLISION_FUNC.append(func)
wp.launch( wp.launch(
_primitive_narrowphase_builder(m), _primitive_narrowphase(_PRIMITIVE_COLLISION_TYPES, _PRIMITIVE_COLLISION_FUNC),
dim=d.naconmax, dim=d.naconmax,
inputs=[ inputs=[
m.geom_type, m.geom_type,
@@ -316,7 +316,7 @@ def plane_box(
box_pos: wp.vec3, box_pos: wp.vec3,
box_rot: wp.mat33, box_rot: wp.mat33,
box_size: wp.vec3, box_size: wp.vec3,
) -> Tuple[wp.vec4, mat43f, wp.vec3]: ) -> Tuple[vec8f, mat83f, wp.vec3]:
"""Core contact geometry calculation for plane-box collision. """Core contact geometry calculation for plane-box collision.
Args: Args:
@@ -325,42 +325,36 @@ def plane_box(
box_pos: Center position of the box. box_pos: Center position of the box.
box_rot: Rotation matrix of the box. box_rot: Rotation matrix of the box.
box_size: Half-extents of the box along each axis. box_size: Half-extents of the box along each axis.
margin: Collision tolerance.
Returns: Returns:
- Vector of contact distances (wp.inf for unpopulated contacts). - Vector of contact distances (wp.inf for unpopulated contacts).
- Matrix of contact positions (one per row). - Matrix of contact positions (one per row).
- Contact normal vector. - Contact normal vector.
""" """
corner = wp.vec3()
center_dist = wp.dot(box_pos - plane_pos, plane_normal) center_dist = wp.dot(box_pos - plane_pos, plane_normal)
dist = wp.vec4(wp.inf) dist = vec8f(wp.inf)
pos = mat43f() pos = mat83f()
# test all corners, pick bottom 4 # test all corners, pick bottom 4
ncontact = int(0)
for i in range(8): for i in range(8):
# get corner in local coordinates # get corner in local coordinates
corner.x = wp.where(i & 1, box_size.x, -box_size.x) corner = wp.vec3(
corner.y = wp.where(i & 2, box_size.y, -box_size.y) wp.where(i & 1, box_size[0], -box_size[0]),
corner.z = wp.where(i & 4, box_size.z, -box_size.z) wp.where(i & 2, box_size[1], -box_size[1]),
wp.where(i & 4, box_size[2], -box_size[2]),
)
# get corner in global coordinates relative to box center # get corner in global coordinates relative to box center
corner = box_rot * corner corner = box_rot * corner
# compute distance to plane, skip if too far or pointing up # compute distance to plane
ldist = wp.dot(plane_normal, corner) ldist = wp.dot(plane_normal, corner)
if center_dist + ldist > 0 or ldist > 0:
continue
cdist = center_dist + ldist cdist = center_dist + ldist
dist[ncontact] = cdist dist[i] = cdist
pos[ncontact] = corner + box_pos - 0.5 * plane_normal * cdist pos[i] = corner + box_pos - 0.5 * plane_normal * cdist
ncontact += 1
if ncontact >= 4:
break
return dist, pos, plane_normal return dist, pos, plane_normal
@@ -586,8 +580,7 @@ def box_box(
box2_pos: Center position of the second box. box2_pos: Center position of the second box.
box2_rot: Rotation matrix of the second box. box2_rot: Rotation matrix of the second box.
box2_size: Half-extents of the second box along each axis. box2_size: Half-extents of the second box along each axis.
margin: Distance threshold for early contact generation (default: 0.0). margin: Collision tolerance.
When positive, contacts are generated before boxes overlap.
Returns: Returns:
- Vector of contact distances (wp.inf for unpopulated contacts). - Vector of contact distances (wp.inf for unpopulated contacts).
@@ -1111,10 +1104,8 @@ def capsule_box(
halfaxis = axis * capsule_half_length # halfaxis is the capsule direction halfaxis = axis * capsule_half_length # halfaxis is the capsule direction
axisdir = wp.int32(halfaxis[0] > 0.0) + 2 * wp.int32(halfaxis[1] > 0.0) + 4 * wp.int32(halfaxis[2] > 0.0) axisdir = wp.int32(halfaxis[0] > 0.0) + 2 * wp.int32(halfaxis[1] > 0.0) + 4 * wp.int32(halfaxis[2] > 0.0)
bestdistmax = 2.0 * (capsule_radius + capsule_half_length + box_size[0] + box_size[1] + box_size[2])
# keep track of closest point # keep track of closest point
bestdist = wp.float32(bestdistmax) bestdist = wp.float32(1.0e32)
bestsegmentpos = wp.float32(-12) bestsegmentpos = wp.float32(-12)
# cltype: encoded collision configuration # cltype: encoded collision configuration
@@ -1253,7 +1244,7 @@ def capsule_box(
p = wp.vec2(pos.x, pos.y) p = wp.vec2(pos.x, pos.y)
dd = wp.vec2(halfaxis.x, halfaxis.y) dd = wp.vec2(halfaxis.x, halfaxis.y)
s = wp.vec2(box_size.x, box_size.y) s = wp.vec2(box_size[0], box_size[1])
secondpos = wp.float32(-4.0) secondpos = wp.float32(-4.0)
uu = dd.x * s.y uu = dd.x * s.y
+123 -16
View File
@@ -38,6 +38,7 @@ def _zero_constraint_counts(
ne_weld_out: wp.array(dtype=int), ne_weld_out: wp.array(dtype=int),
ne_jnt_out: wp.array(dtype=int), ne_jnt_out: wp.array(dtype=int),
ne_ten_out: wp.array(dtype=int), ne_ten_out: wp.array(dtype=int),
ne_flex_out: wp.array(dtype=int),
): ):
worldid = wp.tid() worldid = wp.tid()
@@ -47,6 +48,7 @@ def _zero_constraint_counts(
ne_weld_out[worldid] = 0 ne_weld_out[worldid] = 0
ne_jnt_out[worldid] = 0 ne_jnt_out[worldid] = 0
ne_ten_out[worldid] = 0 ne_ten_out[worldid] = 0
ne_flex_out[worldid] = 0
nf_out[worldid] = 0 nf_out[worldid] = 0
nl_out[worldid] = 0 nl_out[worldid] = 0
nefc_out[worldid] = 0 nefc_out[worldid] = 0
@@ -485,6 +487,81 @@ def _efc_equality_tendon(
) )
@wp.kernel
def _efc_equality_flex(
# Model:
nv: int,
opt_timestep: wp.array(dtype=float),
flexedge_length0: wp.array(dtype=float),
flexedge_invweight0: wp.array(dtype=float),
eq_solref: wp.array2d(dtype=wp.vec2),
eq_solimp: wp.array2d(dtype=vec5),
eq_flex_adr: wp.array(dtype=int),
# Data in:
qvel_in: wp.array2d(dtype=float),
flexedge_J_in: wp.array3d(dtype=float),
flexedge_length_in: wp.array2d(dtype=float),
njmax_in: int,
# In:
refsafe_in: int,
# Data out:
nefc_out: wp.array(dtype=int),
efc_type_out: wp.array2d(dtype=int),
efc_id_out: wp.array2d(dtype=int),
efc_J_out: wp.array3d(dtype=float),
efc_pos_out: wp.array2d(dtype=float),
efc_margin_out: wp.array2d(dtype=float),
efc_D_out: wp.array2d(dtype=float),
efc_vel_out: wp.array2d(dtype=float),
efc_aref_out: wp.array2d(dtype=float),
efc_frictionloss_out: wp.array2d(dtype=float),
ne_flex_out: wp.array(dtype=int),
):
worldid, eqflexid, edgeid = wp.tid()
eqid = eq_flex_adr[eqflexid]
wp.atomic_add(ne_flex_out, worldid, 1)
efcid = wp.atomic_add(nefc_out, worldid, 1)
if efcid >= njmax_in:
return
pos = flexedge_length_in[worldid, edgeid] - flexedge_length0[edgeid]
solref = eq_solref[worldid % eq_solref.shape[0], eqid]
solimp = eq_solimp[worldid % eq_solimp.shape[0], eqid]
Jqvel = float(0.0)
for i in range(nv):
J = flexedge_J_in[worldid, edgeid, i]
efc_J_out[worldid, efcid, i] = J
Jqvel += J * qvel_in[worldid, i]
_update_efc_row(
worldid,
opt_timestep[worldid % opt_timestep.shape[0]],
refsafe_in,
efcid,
pos,
pos,
flexedge_invweight0[edgeid],
solref,
solimp,
0.0,
Jqvel,
0.0,
ConstraintType.EQUALITY,
eqid,
efc_type_out,
efc_id_out,
efc_pos_out,
efc_margin_out,
efc_D_out,
efc_vel_out,
efc_aref_out,
efc_frictionloss_out,
)
@wp.kernel @wp.kernel
def _efc_friction_dof( def _efc_friction_dof(
# Model: # Model:
@@ -1135,7 +1212,7 @@ def _efc_contact_pyramidal(
# Model: # Model:
nv: int, nv: int,
opt_timestep: wp.array(dtype=float), opt_timestep: wp.array(dtype=float),
opt_impratio: wp.array(dtype=float), opt_impratio_invsqrt: wp.array(dtype=float),
body_parentid: wp.array(dtype=int), body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int), body_rootid: wp.array(dtype=int),
body_invweight0: wp.array2d(dtype=wp.vec2), body_invweight0: wp.array2d(dtype=wp.vec2),
@@ -1200,9 +1277,8 @@ def _efc_contact_pyramidal(
contact_efc_address_out[conid, dimid] = -1 contact_efc_address_out[conid, dimid] = -1
return return
opt_timestep_id = worldid % opt_timestep.shape[0] timestep = opt_timestep[worldid % opt_timestep.shape[0]]
timestep = opt_timestep[opt_timestep_id] impratio_invsqrt = opt_impratio_invsqrt[worldid % opt_impratio_invsqrt.shape[0]]
impratio = opt_impratio[opt_timestep_id]
contact_efc_address_out[conid, dimid] = efcid contact_efc_address_out[conid, dimid] = efcid
geom = geom_in[conid] geom = geom_in[conid]
@@ -1223,7 +1299,7 @@ def _efc_contact_pyramidal(
fri0 = friction[0] fri0 = friction[0]
frii = friction[dimid2 - 1] frii = friction[dimid2 - 1]
invweight = invweight + fri0 * fri0 * invweight invweight = invweight + fri0 * fri0 * invweight
invweight = invweight * 2.0 * fri0 * fri0 / impratio invweight = invweight * 2.0 * fri0 * fri0 * impratio_invsqrt * impratio_invsqrt
Jqvel = float(0.0) Jqvel = float(0.0)
for i in range(nv): for i in range(nv):
@@ -1306,7 +1382,7 @@ def _efc_contact_elliptic(
# Model: # Model:
nv: int, nv: int,
opt_timestep: wp.array(dtype=float), opt_timestep: wp.array(dtype=float),
opt_impratio: wp.array(dtype=float), opt_impratio_invsqrt: wp.array(dtype=float),
body_parentid: wp.array(dtype=int), body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int), body_rootid: wp.array(dtype=int),
body_invweight0: wp.array2d(dtype=wp.vec2), body_invweight0: wp.array2d(dtype=wp.vec2),
@@ -1370,9 +1446,8 @@ def _efc_contact_elliptic(
contact_efc_address_out[conid, dimid] = -1 contact_efc_address_out[conid, dimid] = -1
return return
opt_timestep_id = worldid % opt_timestep.shape[0] timestep = opt_timestep[worldid % opt_timestep.shape[0]]
timestep = opt_timestep[opt_timestep_id] impratio_invsqrt = opt_impratio_invsqrt[worldid % opt_impratio_invsqrt.shape[0]]
impratio = opt_impratio[opt_timestep_id]
contact_efc_address_out[conid, dimid] = efcid contact_efc_address_out[conid, dimid] = efcid
geom = geom_in[conid] geom = geom_in[conid]
@@ -1432,8 +1507,7 @@ def _efc_contact_elliptic(
if solreffriction[0] or solreffriction[1]: if solreffriction[0] or solreffriction[1]:
ref = solreffriction ref = solreffriction
# TODO(team): precompute 1 / impratio invweight = invweight * impratio_invsqrt * impratio_invsqrt
invweight = invweight / impratio
friction = friction_in[conid] friction = friction_in[conid]
if dimid > 1: if dimid > 1:
@@ -1482,11 +1556,12 @@ def _num_equality(
ne_weld_in: wp.array(dtype=int), ne_weld_in: wp.array(dtype=int),
ne_jnt_in: wp.array(dtype=int), ne_jnt_in: wp.array(dtype=int),
ne_ten_in: wp.array(dtype=int), ne_ten_in: wp.array(dtype=int),
ne_flex_in: wp.array(dtype=int),
# Data out: # Data out:
ne_out: wp.array(dtype=int), ne_out: wp.array(dtype=int),
): ):
worldid = wp.tid() worldid = wp.tid()
ne = ne_connect_in[worldid] + ne_weld_in[worldid] + ne_jnt_in[worldid] + ne_ten_in[worldid] ne = ne_connect_in[worldid] + ne_weld_in[worldid] + ne_jnt_in[worldid] + ne_ten_in[worldid] + ne_flex_in[worldid]
ne_out[worldid] = ne ne_out[worldid] = ne
@@ -1496,7 +1571,7 @@ def make_constraint(m: types.Model, d: types.Data):
wp.launch( wp.launch(
_zero_constraint_counts, _zero_constraint_counts,
dim=d.nworld, dim=d.nworld,
inputs=[d.ne, d.nf, d.nl, d.nefc, d.ne_connect, d.ne_weld, d.ne_jnt, d.ne_ten], inputs=[d.ne, d.nf, d.nl, d.nefc, d.ne_connect, d.ne_weld, d.ne_jnt, d.ne_ten, d.ne_flex],
) )
if not (m.opt.disableflags & types.DisableBit.CONSTRAINT): if not (m.opt.disableflags & types.DisableBit.CONSTRAINT):
@@ -1663,10 +1738,42 @@ def make_constraint(m: types.Model, d: types.Data):
], ],
) )
wp.launch(
_efc_equality_flex,
dim=(d.nworld, m.eq_flex_adr.size, m.nflexedge),
inputs=[
m.nv,
m.opt.timestep,
m.flexedge_length0,
m.flexedge_invweight0,
m.eq_solref,
m.eq_solimp,
m.eq_flex_adr,
d.qvel,
d.flexedge_J,
d.flexedge_length,
d.njmax,
refsafe,
],
outputs=[
d.nefc,
d.efc.type,
d.efc.id,
d.efc.J,
d.efc.pos,
d.efc.margin,
d.efc.D,
d.efc.vel,
d.efc.aref,
d.efc.frictionloss,
d.ne_flex,
],
)
wp.launch( wp.launch(
_num_equality, _num_equality,
dim=d.nworld, dim=d.nworld,
inputs=[d.ne_connect, d.ne_weld, d.ne_jnt, d.ne_ten], inputs=[d.ne_connect, d.ne_weld, d.ne_jnt, d.ne_ten, d.ne_flex],
outputs=[d.ne], outputs=[d.ne],
) )
@@ -1847,7 +1954,7 @@ def make_constraint(m: types.Model, d: types.Data):
inputs=[ inputs=[
m.nv, m.nv,
m.opt.timestep, m.opt.timestep,
m.opt.impratio, m.opt.impratio_invsqrt,
m.body_parentid, m.body_parentid,
m.body_rootid, m.body_rootid,
m.body_invweight0, m.body_invweight0,
@@ -1892,7 +1999,7 @@ def make_constraint(m: types.Model, d: types.Data):
inputs=[ inputs=[
m.nv, m.nv,
m.opt.timestep, m.opt.timestep,
m.opt.impratio, m.opt.impratio_invsqrt,
m.body_parentid, m.body_parentid,
m.body_rootid, m.body_rootid,
m.body_invweight0, m.body_invweight0,
+150 -53
View File
@@ -21,21 +21,18 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import DisableBit
from mujoco.mjx.third_party.mujoco_warp._src.types import DynType from mujoco.mjx.third_party.mujoco_warp._src.types import DynType
from mujoco.mjx.third_party.mujoco_warp._src.types import GainType from mujoco.mjx.third_party.mujoco_warp._src.types import GainType
from mujoco.mjx.third_party.mujoco_warp._src.types import Model from mujoco.mjx.third_party.mujoco_warp._src.types import Model
from mujoco.mjx.third_party.mujoco_warp._src.types import TileSet
from mujoco.mjx.third_party.mujoco_warp._src.types import vec10f from mujoco.mjx.third_party.mujoco_warp._src.types import vec10f
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False}) wp.set_module_options({"enable_backward": False})
# TODO(team): improve performance with tile operations?
@wp.kernel @wp.kernel
def _qderiv_actuator_passive( def _qderiv_actuator_passive_vel(
# Model: # Model:
nu: int,
opt_timestep: wp.array(dtype=float),
opt_disableflags: int,
opt_is_sparse: bool,
dof_damping: wp.array2d(dtype=float),
actuator_dyntype: wp.array(dtype=int), actuator_dyntype: wp.array(dtype=int),
actuator_gaintype: wp.array(dtype=int), actuator_gaintype: wp.array(dtype=int),
actuator_biastype: wp.array(dtype=int), actuator_biastype: wp.array(dtype=int),
@@ -46,9 +43,75 @@ def _qderiv_actuator_passive(
# Data in: # Data in:
act_in: wp.array2d(dtype=float), act_in: wp.array2d(dtype=float),
ctrl_in: wp.array2d(dtype=float), ctrl_in: wp.array2d(dtype=float),
# Out:
vel_out: wp.array2d(dtype=float),
):
worldid, actid = wp.tid()
actuator_gainprm_id = worldid % actuator_gainprm.shape[0]
actuator_biasprm_id = worldid % actuator_biasprm.shape[0]
if actuator_gaintype[actid] == GainType.AFFINE:
gain = actuator_gainprm[actuator_gainprm_id, actid][2]
else:
gain = 0.0
if actuator_biastype[actid] == BiasType.AFFINE:
bias = actuator_biasprm[actuator_biasprm_id, actid][2]
else:
bias = 0.0
if bias == 0.0 and gain == 0.0:
vel_out[worldid, actid] = 0.0
return
vel = float(bias)
if actuator_dyntype[actid] != DynType.NONE:
if gain != 0.0:
act_first = actuator_actadr[actid]
act_last = act_first + actuator_actnum[actid] - 1
vel += gain * act_in[worldid, act_last]
else:
if gain != 0.0:
vel += gain * ctrl_in[worldid, actid]
vel_out[worldid, actid] = vel
@cache_kernel
def _qderiv_actuator_passive_actuation_dense(tile: TileSet, nu: int):
@nested_kernel(module="unique", enable_backward=False)
def kernel(
# Data in:
vel_in: wp.array3d(dtype=float),
actuator_moment_in: wp.array3d(dtype=float),
# In:
adr: wp.array(dtype=int),
# Out:
qDeriv_out: wp.array3d(dtype=float),
):
worldid, nodeid = wp.tid()
TILE_SIZE = wp.static(tile.size)
NU = wp.static(nu)
dofid = adr[nodeid]
vel_tile = wp.tile_load(vel_in[worldid], shape=(NU, 1), bounds_check=False)
moment_tile = wp.tile_load(actuator_moment_in[worldid], shape=(NU, TILE_SIZE), offset=(0, dofid), bounds_check=False)
moment_weighted = wp.tile_map(wp.mul, wp.tile_broadcast(vel_tile, shape=(NU, TILE_SIZE)), moment_tile)
qderiv_tile = wp.tile_matmul(wp.tile_transpose(moment_tile), moment_weighted)
wp.tile_store(qDeriv_out[worldid], qderiv_tile, offset=(dofid, dofid), bounds_check=False)
return kernel
@wp.kernel
def _qderiv_actuator_passive_actuation_sparse(
# Model:
nu: int,
# Data in:
actuator_moment_in: wp.array3d(dtype=float), actuator_moment_in: wp.array3d(dtype=float),
qM_in: wp.array3d(dtype=float),
# In: # In:
vel_in: wp.array2d(dtype=float),
qMi: wp.array(dtype=int), qMi: wp.array(dtype=int),
qMj: wp.array(dtype=int), qMj: wp.array(dtype=int),
# Out: # Out:
@@ -58,40 +121,45 @@ def _qderiv_actuator_passive(
dofiid = qMi[elemid] dofiid = qMi[elemid]
dofjid = qMj[elemid] dofjid = qMj[elemid]
qderiv_contrib = float(0.0)
for actid in range(nu):
vel = vel_in[worldid, actid]
if vel == 0.0:
continue
qderiv = float(0.0) moment_i = actuator_moment_in[worldid, actid, dofiid]
if not opt_disableflags & DisableBit.ACTUATION: moment_j = actuator_moment_in[worldid, actid, dofjid]
actuator_gainprm_id = worldid % actuator_gainprm.shape[0]
actuator_biasprm_id = worldid % actuator_biasprm.shape[0]
for actid in range(nu): qderiv_contrib += moment_i * moment_j * vel
if actuator_gaintype[actid] == GainType.AFFINE:
gain = actuator_gainprm[actuator_gainprm_id, actid][2]
else:
gain = 0.0
if actuator_biastype[actid] == BiasType.AFFINE: qDeriv_out[worldid, 0, elemid] = qderiv_contrib
bias = actuator_biasprm[actuator_biasprm_id, actid][2]
else:
bias = 0.0
if bias == 0.0 and gain == 0.0:
continue
vel = bias @wp.kernel
if actuator_dyntype[actid] != DynType.NONE: def _qderiv_actuator_passive(
if gain != 0.0: # Model:
act_first = actuator_actadr[actid] opt_timestep: wp.array(dtype=float),
act_last = act_first + actuator_actnum[actid] - 1 opt_disableflags: int,
vel += gain * act_in[worldid, act_last] opt_is_sparse: bool,
else: dof_damping: wp.array2d(dtype=float),
if gain != 0.0: # Data in:
vel += gain * ctrl_in[worldid, actid] qM_in: wp.array3d(dtype=float),
# In:
qMi: wp.array(dtype=int),
qMj: wp.array(dtype=int),
qDeriv_in: wp.array3d(dtype=float),
# Out:
qDeriv_out: wp.array3d(dtype=float),
):
worldid, elemid = wp.tid()
if vel != 0.0: dofiid = qMi[elemid]
qderiv += actuator_moment_in[worldid, actid, dofiid] * actuator_moment_in[worldid, actid, dofjid] * vel dofjid = qMj[elemid]
# TODO(team): fluid model derivative if opt_is_sparse:
qderiv = qDeriv_in[worldid, 0, elemid]
else:
qderiv = qDeriv_in[worldid, dofiid, dofjid]
if not opt_disableflags & DisableBit.DAMPER and dofiid == dofjid: if not opt_disableflags & DisableBit.DAMPER and dofiid == dofjid:
qderiv -= dof_damping[worldid % dof_damping.shape[0], dofiid] qderiv -= dof_damping[worldid % dof_damping.shape[0], dofiid]
@@ -143,53 +211,82 @@ def _qderiv_tendon_damping(
@event_scope @event_scope
def deriv_smooth_vel(m: Model, d: Data, qDeriv: wp.array2d(dtype=float)): def deriv_smooth_vel(m: Model, d: Data, out: wp.array2d(dtype=float)):
"""Analytical derivative of smooth forces w.r.t. velocities. """Analytical derivative of smooth forces w.r.t. velocities.
Args: Args:
m: The model containing kinematic and dynamic information (device). m: The model containing kinematic and dynamic information (device).
d: The data object containing the current state and output arrays (device). d: The data object containing the current state and output arrays (device).
qDeriv: Analytical derivative of smooth forces w.r.t. velocity. out: qM - dt * qDeriv (derivatives of smooth forces w.r.t velocities).
""" """
qMi = m.qM_fullm_i if m.opt.is_sparse else m.dof_tri_row qMi = m.qM_fullm_i
qMj = m.qM_fullm_j if m.opt.is_sparse else m.dof_tri_col qMj = m.qM_fullm_j
# TODO(team): implicit requires different sparsity structure
if ~(m.opt.disableflags & (DisableBit.ACTUATION | DisableBit.DAMPER)): if ~(m.opt.disableflags & (DisableBit.ACTUATION | DisableBit.DAMPER)):
# TODO(team): only clear elements not set by _qderiv_actuator_passive
out.zero_()
if m.nu > 0 and not m.opt.disableflags & DisableBit.ACTUATION:
vel = wp.empty((d.nworld, m.nu), dtype=float)
wp.launch(
_qderiv_actuator_passive_vel,
dim=(d.nworld, m.nu),
inputs=[
m.actuator_dyntype,
m.actuator_gaintype,
m.actuator_biastype,
m.actuator_actadr,
m.actuator_actnum,
m.actuator_gainprm,
m.actuator_biasprm,
d.act,
d.ctrl,
],
outputs=[vel],
)
if m.opt.is_sparse:
wp.launch(
_qderiv_actuator_passive_actuation_sparse,
dim=(d.nworld, qMi.size),
inputs=[m.nu, d.actuator_moment, vel, qMi, qMj],
outputs=[out],
)
else:
vel_3d = vel.reshape(vel.shape + (1,))
for tile in m.qM_tiles:
wp.launch_tiled(
_qderiv_actuator_passive_actuation_dense(tile, m.nu),
dim=(d.nworld, tile.adr.size),
inputs=[vel_3d, d.actuator_moment, tile.adr],
outputs=[out],
block_dim=m.block_dim.mul_m_dense,
)
wp.launch( wp.launch(
_qderiv_actuator_passive, _qderiv_actuator_passive,
dim=(d.nworld, qMi.size), dim=(d.nworld, qMi.size),
inputs=[ inputs=[
m.nu,
m.opt.timestep, m.opt.timestep,
m.opt.disableflags, m.opt.disableflags,
m.opt.is_sparse, m.opt.is_sparse,
m.dof_damping, m.dof_damping,
m.actuator_dyntype,
m.actuator_gaintype,
m.actuator_biastype,
m.actuator_actadr,
m.actuator_actnum,
m.actuator_gainprm,
m.actuator_biasprm,
d.act,
d.ctrl,
d.actuator_moment,
d.qM, d.qM,
qMi, qMi,
qMj, qMj,
out,
], ],
outputs=[qDeriv], outputs=[out],
) )
else: else:
# TODO(team): directly utilize qM for these settings # TODO(team): directly utilize qM for these settings
wp.copy(qDeriv, d.qM) wp.copy(out, d.qM)
if not m.opt.disableflags & DisableBit.DAMPER: if not m.opt.disableflags & DisableBit.DAMPER:
wp.launch( wp.launch(
_qderiv_tendon_damping, _qderiv_tendon_damping,
dim=(d.nworld, qMi.size), dim=(d.nworld, qMi.size),
inputs=[m.ntendon, m.opt.timestep, m.opt.is_sparse, m.tendon_damping, d.ten_J, qMi, qMj], inputs=[m.ntendon, m.opt.timestep, m.opt.is_sparse, m.tendon_damping, d.ten_J, qMi, qMj],
outputs=[qDeriv], outputs=[out],
) )
# TODO(team): rne derivative # TODO(team): rne derivative
@@ -509,6 +509,7 @@ def fwd_position(m: Model, d: Data, factorize: bool = True):
smooth.kinematics(m, d) smooth.kinematics(m, d)
smooth.com_pos(m, d) smooth.com_pos(m, d)
smooth.camlight(m, d) smooth.camlight(m, d)
smooth.flex(m, d)
smooth.tendon(m, d) smooth.tendon(m, d)
smooth.crb(m, d) smooth.crb(m, d)
smooth.tendon_armature(m, d) smooth.tendon_armature(m, d)
+3 -1
View File
@@ -121,7 +121,9 @@ def inv_constraint(m: Model, d: Data):
return return
# update # update
solver.create_context(m, d, grad=False) h = wp.empty((d.nworld, 0, 0), dtype=float) # not used
hfactor = wp.empty((d.nworld, 0, 0), dtype=float) # not used
solver.create_context(m, d, h, hfactor, grad=False)
def inverse(m: Model, d: Data): def inverse(m: Model, d: Data):
+102 -57
View File
@@ -49,6 +49,16 @@ def _create_array(data: Any, spec: wp.array, sizes: dict[str, int]) -> Union[wp.
return array return array
def is_sparse(mjm: mujoco.MjModel) -> bool:
if mjm.opt.jacobian == mujoco.mjtJacobian.mjJAC_AUTO:
if mjm.nv > 32:
return True
else:
return False
else:
return bool(mujoco.mj_isSparse(mjm))
def put_model(mjm: mujoco.MjModel) -> types.Model: def put_model(mjm: mujoco.MjModel) -> types.Model:
"""Creates a model on device. """Creates a model on device.
@@ -93,9 +103,6 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
if field & ~np.bitwise_or.reduce(field_type): if field & ~np.bitwise_or.reduce(field_type):
raise NotImplementedError(f"{field_type.__name__} {field} is unsupported.") raise NotImplementedError(f"{field_type.__name__} {field} is unsupported.")
if mjm.nflex > 1:
raise NotImplementedError("Only one flex is unsupported.")
if ((mjm.flex_contype != 0) | (mjm.flex_conaffinity != 0)).any(): if ((mjm.flex_contype != 0) | (mjm.flex_conaffinity != 0)).any():
raise NotImplementedError("Flex collisions are not implemented.") raise NotImplementedError("Flex collisions are not implemented.")
@@ -137,23 +144,20 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
return True return True
return False return False
for geoms in [ for objtype, objid, reftype, refid in zip(
(types.GeomType.BOX, types.GeomType.BOX), mjm.sensor_objtype[is_collision_sensor],
(types.GeomType.CAPSULE, types.GeomType.BOX), mjm.sensor_objid[is_collision_sensor],
(types.GeomType.CYLINDER, types.GeomType.BOX), mjm.sensor_reftype[is_collision_sensor],
(types.GeomType.PLANE, types.GeomType.BOX), mjm.sensor_refid[is_collision_sensor],
]: ):
for objtype, objid, reftype, refid in zip( if not_implemented(objtype, objid, types.GeomType.BOX) and not_implemented(reftype, refid, types.GeomType.BOX):
mjm.sensor_objtype[is_collision_sensor], raise NotImplementedError(f"Collision sensors with box-box collisions are not implemented.")
mjm.sensor_objid[is_collision_sensor],
mjm.sensor_reftype[is_collision_sensor],
mjm.sensor_refid[is_collision_sensor],
):
if not_implemented(objtype, objid, geoms[0]) and not_implemented(reftype, refid, geoms[1]):
raise NotImplementedError(f"Collision sensors with {geoms[0]} and {geoms[1]} are not implemented.")
# create opt # create opt
opt = types.Option(**{f.name: getattr(mjm.opt, f.name, None) for f in dataclasses.fields(types.Option)}) opt_kwargs = {f.name: getattr(mjm.opt, f.name, None) for f in dataclasses.fields(types.Option)}
if hasattr(mjm.opt, "impratio"):
opt_kwargs["impratio_invsqrt"] = 1.0 / np.sqrt(np.maximum(mjm.opt.impratio, mujoco.mjMINVAL))
opt = types.Option(**opt_kwargs)
# C MuJoCo tolerance was chosen for float64 architecture, but we default to float32 on GPU # C MuJoCo tolerance was chosen for float64 architecture, but we default to float32 on GPU
# adjust the tolerance for lower precision, to avoid the solver spending iterations needlessly # adjust the tolerance for lower precision, to avoid the solver spending iterations needlessly
@@ -161,7 +165,7 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
opt.tolerance = max(opt.tolerance, 1e-6) opt.tolerance = max(opt.tolerance, 1e-6)
# warp only fields # warp only fields
opt.is_sparse = bool(mujoco.mj_isSparse(mjm)) opt.is_sparse = is_sparse(mjm)
ls_parallel_id = mujoco.mj_name2id(mjm, mujoco.mjtObj.mjOBJ_NUMERIC, "ls_parallel") ls_parallel_id = mujoco.mj_name2id(mjm, mujoco.mjtObj.mjOBJ_NUMERIC, "ls_parallel")
opt.ls_parallel = (ls_parallel_id > -1) and (mjm.numeric_data[mjm.numeric_adr[ls_parallel_id]] == 1) opt.ls_parallel = (ls_parallel_id > -1) and (mjm.numeric_data[mjm.numeric_adr[ls_parallel_id]] == 1)
opt.ls_parallel_min_step = 1.0e-6 # TODO(team): determine good default setting opt.ls_parallel_min_step = 1.0e-6 # TODO(team): determine good default setting
@@ -170,7 +174,11 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
opt.broadphase_filter = types.BroadphaseFilter.PLANE | types.BroadphaseFilter.SPHERE | types.BroadphaseFilter.OBB opt.broadphase_filter = types.BroadphaseFilter.PLANE | types.BroadphaseFilter.SPHERE | types.BroadphaseFilter.OBB
opt.graph_conditional = True opt.graph_conditional = True
opt.run_collision_detection = True opt.run_collision_detection = True
opt.contact_sensor_maxmatch = 64 contact_sensor_maxmatch_id = mujoco.mj_name2id(mjm, mujoco.mjtObj.mjOBJ_NUMERIC, "contact_sensor_maxmatch")
if contact_sensor_maxmatch_id > -1:
opt.contact_sensor_maxmatch = mjm.numeric_data[mjm.numeric_adr[contact_sensor_maxmatch_id]]
else:
opt.contact_sensor_maxmatch = 64
# place opt on device # place opt on device
for f in dataclasses.fields(types.Option): for f in dataclasses.fields(types.Option):
@@ -188,6 +196,9 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
m.opt = opt m.opt = opt
m.stat = stat m.stat = stat
m.nv_pad = _get_padded_sizes(
mjm.nv, 0, is_sparse(mjm), types.TILE_SIZE_JTDAJ_SPARSE if is_sparse(mjm) else types.TILE_SIZE_JTDAJ_DENSE
)[1]
m.nacttrnbody = (mjm.actuator_trntype == mujoco.mjtTrn.mjTRN_BODY).sum() m.nacttrnbody = (mjm.actuator_trntype == mujoco.mjtTrn.mjTRN_BODY).sum()
m.nsensortaxel = mjm.mesh_vertnum[mjm.sensor_objid[mjm.sensor_type == mujoco.mjtSensor.mjSENS_TACTILE]].sum() m.nsensortaxel = mjm.mesh_vertnum[mjm.sensor_objid[mjm.sensor_type == mujoco.mjtSensor.mjSENS_TACTILE]].sum()
m.nsensorcontact = (mjm.sensor_type == mujoco.mjtSensor.mjSENS_CONTACT).sum() m.nsensorcontact = (mjm.sensor_type == mujoco.mjtSensor.mjSENS_CONTACT).sum()
@@ -249,10 +260,12 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
nxn_pairid_contact[upper_tri_index(mjm.ngeom, mjm.pair_geom1[i], mjm.pair_geom2[i])] = i nxn_pairid_contact[upper_tri_index(mjm.ngeom, mjm.pair_geom1[i], mjm.pair_geom2[i])] = i
sensor_collision_adr = np.nonzero(is_collision_sensor)[0] sensor_collision_adr = np.nonzero(is_collision_sensor)[0]
collision_sensor_adr = np.full(mjm.nsensor, -1)
collision_sensor_adr[sensor_collision_adr] = np.arange(len(sensor_collision_adr))
nxn_pairid_collision = -1 * np.ones(len(geom1), dtype=int) nxn_pairid_collision = -1 * np.ones(len(geom1), dtype=int)
pairids = [] pairids = []
collision_geom_adr = [0] sensor_collision_start_adr = []
m.sensor_collision_start_adr = []
for i in range(sensor_collision_adr.size): for i in range(sensor_collision_adr.size):
sensorid = sensor_collision_adr[i] sensorid = sensor_collision_adr[i]
objtype = mjm.sensor_objtype[sensorid] objtype = mjm.sensor_objtype[sensorid]
@@ -275,24 +288,20 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
id2 = refid id2 = refid
# collide all pairs # collide all pairs
geomid = 0
for geom1id in range(id1, id1 + n1): for geom1id in range(id1, id1 + n1):
for geom2id in range(id2, id2 + n2): for geom2id in range(id2, id2 + n2):
pairid = upper_tri_index(mjm.ngeom, geom1id, geom2id) pairid = upper_tri_index(mjm.ngeom, geom1id, geom2id)
if pairid in pairids: if pairid in pairids:
m.sensor_collision_start_adr.append(nxn_pairid_collision[pairid]) sensor_collision_start_adr.append(nxn_pairid_collision[pairid])
else: else:
npairids = len(pairids)
nxn_pairid_collision[pairid] = npairids
sensor_collision_start_adr.append(npairids)
pairids.append(pairid) pairids.append(pairid)
adr = collision_geom_adr[-1] + geomid
nxn_pairid_collision[pairid] = adr
m.sensor_collision_start_adr.append(adr)
geomid += 1
if i < sensor_collision_adr.size - 1:
collision_geom_adr.append(collision_geom_adr[-1] + n1 * n2)
m.nsensorcollision = (nxn_pairid_collision >= 0).sum() m.nsensorcollision = (nxn_pairid_collision >= 0).sum()
m.sensor_collision_start_adr = np.array(sensor_collision_start_adr)
nxn_include = (nxn_pairid_contact > -2) | (nxn_pairid_collision >= 0) nxn_include = (nxn_pairid_contact > -2) | (nxn_pairid_collision >= 0)
if nxn_include.sum() < 250_000: if nxn_include.sum() < 250_000:
@@ -363,6 +372,7 @@ def put_model(mjm: mujoco.MjModel) -> types.Model:
m.eq_wld_adr = np.nonzero(mjm.eq_type == types.EqType.WELD)[0] m.eq_wld_adr = np.nonzero(mjm.eq_type == types.EqType.WELD)[0]
m.eq_jnt_adr = np.nonzero(mjm.eq_type == types.EqType.JOINT)[0] m.eq_jnt_adr = np.nonzero(mjm.eq_type == types.EqType.JOINT)[0]
m.eq_ten_adr = np.nonzero(mjm.eq_type == types.EqType.TENDON)[0] m.eq_ten_adr = np.nonzero(mjm.eq_type == types.EqType.TENDON)[0]
m.eq_flex_adr = np.nonzero(mjm.eq_type == types.EqType.FLEX)[0]
# fixed tendon # fixed tendon
m.tendon_jnt_adr, m.wrap_jnt_adr = [], [] m.tendon_jnt_adr, m.wrap_jnt_adr = [], []
@@ -545,7 +555,7 @@ def _get_padded_sizes(nv: int, njmax: int, is_sparse: bool, tile_size: int):
return ((x + multiple - 1) // multiple) * multiple return ((x + multiple - 1) // multiple) * multiple
njmax_padded = round_up(njmax, tile_size) njmax_padded = round_up(njmax, tile_size)
nv_padded = round_up(nv, tile_size) if is_sparse else round_up(nv, 4) nv_padded = round_up(nv, tile_size) if (is_sparse or nv > 32) else round_up(nv, 4)
return njmax_padded, nv_padded return njmax_padded, nv_padded
@@ -592,8 +602,8 @@ def make_data(
sizes = dict({"*": 1}, **{f.name: getattr(mjm, f.name, None) for f in dataclasses.fields(types.Model) if f.type is int}) sizes = dict({"*": 1}, **{f.name: getattr(mjm, f.name, None) for f in dataclasses.fields(types.Model) if f.type is int})
sizes["nmaxcondim"] = np.concatenate(([0], mjm.geom_condim, mjm.pair_dim)).max() sizes["nmaxcondim"] = np.concatenate(([0], mjm.geom_condim, mjm.pair_dim)).max()
sizes["nmaxpyramid"] = np.maximum(1, 2 * (sizes["nmaxcondim"] - 1)) sizes["nmaxpyramid"] = np.maximum(1, 2 * (sizes["nmaxcondim"] - 1))
tile_size = types.TILE_SIZE_JTDAJ_SPARSE if mujoco.mj_isSparse(mjm) else types.TILE_SIZE_JTDAJ_DENSE tile_size = types.TILE_SIZE_JTDAJ_SPARSE if is_sparse(mjm) else types.TILE_SIZE_JTDAJ_DENSE
sizes["njmax_pad"], sizes["nv_pad"] = _get_padded_sizes(mjm.nv, njmax, mujoco.mj_isSparse(mjm), tile_size) sizes["njmax_pad"], sizes["nv_pad"] = _get_padded_sizes(mjm.nv, njmax, is_sparse(mjm), tile_size)
sizes["nworld"] = nworld sizes["nworld"] = nworld
sizes["naconmax"] = naconmax sizes["naconmax"] = naconmax
sizes["njmax"] = njmax sizes["njmax"] = njmax
@@ -601,7 +611,18 @@ def make_data(
contact = types.Contact(**{f.name: _create_array(None, f.type, sizes) for f in dataclasses.fields(types.Contact)}) contact = types.Contact(**{f.name: _create_array(None, f.type, sizes) for f in dataclasses.fields(types.Contact)})
efc = types.Constraint(**{f.name: _create_array(None, f.type, sizes) for f in dataclasses.fields(types.Constraint)}) efc = types.Constraint(**{f.name: _create_array(None, f.type, sizes) for f in dataclasses.fields(types.Constraint)})
# world body and static geom (attached to the world) poses are precomputed
# this speeds up scenes with many static geoms (e.g. terrains)
# TODO(team): remove this when we introduce dof islands + sleeping
mjd = mujoco.MjData(mjm)
mujoco.mj_kinematics(mjm, mjd)
# mocap
mocap_body = np.nonzero(mjm.body_mocapid >= 0)[0]
mocap_id = mjm.body_mocapid[mocap_body]
d_kwargs = { d_kwargs = {
"qpos": wp.array(np.tile(mjm.qpos0, nworld), shape=(nworld, mjm.nq), dtype=float),
"contact": contact, "contact": contact,
"efc": efc, "efc": efc,
"nworld": nworld, "nworld": nworld,
@@ -609,8 +630,20 @@ def make_data(
"njmax": njmax, "njmax": njmax,
"qM": None, "qM": None,
"qLD": None, "qLD": None,
"geom_xpos": None, # world body
"geom_xmat": None, "xquat": wp.array(np.tile(mjd.xquat, (nworld, 1)), shape=(nworld, mjm.nbody), dtype=wp.quat),
"xmat": wp.array(np.tile(mjd.xmat, (nworld, 1)), shape=(nworld, mjm.nbody), dtype=wp.mat33),
"ximat": wp.array(np.tile(mjd.ximat, (nworld, 1)), shape=(nworld, mjm.nbody), dtype=wp.mat33),
# static geoms
"geom_xpos": wp.array(np.tile(mjd.geom_xpos, (nworld, 1)), shape=(nworld, mjm.ngeom), dtype=wp.vec3),
"geom_xmat": wp.array(np.tile(mjd.geom_xmat, (nworld, 1)), shape=(nworld, mjm.ngeom), dtype=wp.mat33),
# mocap
"mocap_pos": wp.array(np.tile(mjm.body_pos[mocap_body[mocap_id]], (nworld, 1)), shape=(nworld, mjm.nmocap), dtype=wp.vec3),
"mocap_quat": wp.array(
np.tile(mjm.body_quat[mocap_body[mocap_id]], (nworld, 1)), shape=(nworld, mjm.nmocap), dtype=wp.quat
),
# equality constraints
"eq_active": wp.array(np.tile(mjm.eq_active0.astype(bool), (nworld, 1)), shape=(nworld, mjm.neq), dtype=bool),
} }
for f in dataclasses.fields(types.Data): for f in dataclasses.fields(types.Data):
if f.name in d_kwargs: if f.name in d_kwargs:
@@ -619,21 +652,13 @@ def make_data(
d = types.Data(**d_kwargs) d = types.Data(**d_kwargs)
if mujoco.mj_isSparse(mjm): if is_sparse(mjm):
d.qM = wp.zeros((nworld, 1, mjm.nM), dtype=float) d.qM = wp.zeros((nworld, 1, mjm.nM), dtype=float)
d.qLD = wp.zeros((nworld, 1, mjm.nC), dtype=float) d.qLD = wp.zeros((nworld, 1, mjm.nC), dtype=float)
else: else:
d.qM = wp.zeros((nworld, sizes["nv_pad"], sizes["nv_pad"]), dtype=float) d.qM = wp.zeros((nworld, sizes["nv_pad"], sizes["nv_pad"]), dtype=float)
d.qLD = wp.zeros((nworld, mjm.nv, mjm.nv), dtype=float) d.qLD = wp.zeros((nworld, mjm.nv, mjm.nv), dtype=float)
# static geoms (attached to the world) have their poses calculated once during make_data instead
# of during each physics step. this speeds up scenes with many static geoms (e.g. terrains)
# TODO(team): remove this when we introduce dof islands + sleeping
mjd = mujoco.MjData(mjm)
mujoco.mj_kinematics(mjm, mjd)
d.geom_xpos = wp.array(np.tile(mjd.geom_xpos, (nworld, 1)), shape=(nworld, mjm.ngeom), dtype=wp.vec3)
d.geom_xmat = wp.array(np.tile(mjd.geom_xmat, (nworld, 1)), shape=(nworld, mjm.ngeom), dtype=wp.mat33)
return d return d
@@ -691,8 +716,8 @@ def put_data(
sizes = dict({"*": 1}, **{f.name: getattr(mjm, f.name, None) for f in dataclasses.fields(types.Model) if f.type is int}) sizes = dict({"*": 1}, **{f.name: getattr(mjm, f.name, None) for f in dataclasses.fields(types.Model) if f.type is int})
sizes["nmaxcondim"] = np.concatenate(([0], mjm.geom_condim, mjm.pair_dim)).max() sizes["nmaxcondim"] = np.concatenate(([0], mjm.geom_condim, mjm.pair_dim)).max()
sizes["nmaxpyramid"] = np.maximum(1, 2 * (sizes["nmaxcondim"] - 1)) sizes["nmaxpyramid"] = np.maximum(1, 2 * (sizes["nmaxcondim"] - 1))
tile_size = types.TILE_SIZE_JTDAJ_SPARSE if mujoco.mj_isSparse(mjm) else types.TILE_SIZE_JTDAJ_DENSE tile_size = types.TILE_SIZE_JTDAJ_SPARSE if is_sparse(mjm) else types.TILE_SIZE_JTDAJ_DENSE
sizes["njmax_pad"], sizes["nv_pad"] = _get_padded_sizes(mjm.nv, njmax, mujoco.mj_isSparse(mjm), tile_size) sizes["njmax_pad"], sizes["nv_pad"] = _get_padded_sizes(mjm.nv, njmax, is_sparse(mjm), tile_size)
sizes["nworld"] = nworld sizes["nworld"] = nworld
sizes["naconmax"] = naconmax sizes["naconmax"] = naconmax
sizes["njmax"] = njmax sizes["njmax"] = njmax
@@ -766,11 +791,13 @@ def put_data(
"qLD": None, "qLD": None,
"ten_J": None, "ten_J": None,
"actuator_moment": None, "actuator_moment": None,
"flexedge_J": None,
"nacon": None, "nacon": None,
"ne_connect": None, "ne_connect": None,
"ne_weld": None, "ne_weld": None,
"ne_jnt": None, "ne_jnt": None,
"ne_ten": None, "ne_ten": None,
"ne_flex": None,
"nsolving": None, "nsolving": None,
} }
for f in dataclasses.fields(types.Data): for f in dataclasses.fields(types.Data):
@@ -785,12 +812,9 @@ def put_data(
d = types.Data(**d_kwargs) d = types.Data(**d_kwargs)
d.solver_niter = wp.full((nworld,), mjd.solver_niter[0], dtype=int) d.solver_niter = wp.full((nworld,), mjd.solver_niter[0], dtype=int)
if mujoco.mj_isSparse(mjm): if is_sparse(mjm):
d.qM = wp.array(np.full((nworld, 1, mjm.nM), mjd.qM), dtype=float) d.qM = wp.array(np.full((nworld, 1, mjm.nM), mjd.qM), dtype=float)
d.qLD = wp.array(np.full((nworld, 1, mjm.nC), mjd.qLD), dtype=float) d.qLD = wp.array(np.full((nworld, 1, mjm.nC), mjd.qLD), dtype=float)
ten_J = np.zeros((mjm.ntendon, mjm.nv))
mujoco.mju_sparse2dense(ten_J, mjd.ten_J.reshape(-1), mjd.ten_J_rownnz, mjd.ten_J_rowadr, mjd.ten_J_colind.reshape(-1))
d.ten_J = wp.array(np.full((nworld, mjm.ntendon, mjm.nv), ten_J), dtype=float)
else: else:
qM = np.zeros((mjm.nv, mjm.nv)) qM = np.zeros((mjm.nv, mjm.nv))
mujoco.mj_fullM(mjm, qM, mjd.qM) mujoco.mj_fullM(mjm, qM, mjd.qM)
@@ -799,8 +823,21 @@ def put_data(
qM_padded = np.pad(qM, ((0, padding), (0, padding)), mode="constant", constant_values=0.0) qM_padded = np.pad(qM, ((0, padding), (0, padding)), mode="constant", constant_values=0.0)
d.qM = wp.array(np.full((nworld, sizes["nv_pad"], sizes["nv_pad"]), qM_padded), dtype=float) d.qM = wp.array(np.full((nworld, sizes["nv_pad"], sizes["nv_pad"]), qM_padded), dtype=float)
d.qLD = wp.array(np.full((nworld, mjm.nv, mjm.nv), qLD), dtype=float) d.qLD = wp.array(np.full((nworld, mjm.nv, mjm.nv), qLD), dtype=float)
if mujoco.mj_isSparse(mjm):
ten_J = np.zeros((mjm.ntendon, mjm.nv))
mujoco.mju_sparse2dense(ten_J, mjd.ten_J.reshape(-1), mjd.ten_J_rownnz, mjd.ten_J_rowadr, mjd.ten_J_colind.reshape(-1))
d.ten_J = wp.array(np.full((nworld, mjm.ntendon, mjm.nv), ten_J), dtype=float)
flexedge_J = np.zeros((mjm.nflexedge, mjm.nv))
mujoco.mju_sparse2dense(
flexedge_J, mjd.flexedge_J.reshape(-1), mjd.flexedge_J_rownnz, mjd.flexedge_J_rowadr, mjd.flexedge_J_colind.reshape(-1)
)
d.flexedge_J = wp.array(np.full((nworld, mjm.nflexedge, mjm.nv), flexedge_J), dtype=float)
else:
ten_J = mjd.ten_J.reshape((mjm.ntendon, mjm.nv)) ten_J = mjd.ten_J.reshape((mjm.ntendon, mjm.nv))
d.ten_J = wp.array(np.full((nworld, mjm.ntendon, mjm.nv), ten_J), dtype=float) d.ten_J = wp.array(np.full((nworld, mjm.ntendon, mjm.nv), ten_J), dtype=float)
flexedge_J = mjd.flexedge_J.reshape((mjm.nflexedge, mjm.nv))
d.flexedge_J = wp.array(np.full((nworld, mjm.nflexedge, mjm.nv), flexedge_J), dtype=float)
# TODO(taylorhowell): sparse actuator_moment # TODO(taylorhowell): sparse actuator_moment
actuator_moment = np.zeros((mjm.nu, mjm.nv)) actuator_moment = np.zeros((mjm.nu, mjm.nv))
@@ -812,6 +849,7 @@ def put_data(
d.ne_weld = wp.full(nworld, 6 * np.sum((mjm.eq_type == mujoco.mjtEq.mjEQ_WELD) & mjd.eq_active), dtype=int) d.ne_weld = wp.full(nworld, 6 * np.sum((mjm.eq_type == mujoco.mjtEq.mjEQ_WELD) & mjd.eq_active), dtype=int)
d.ne_jnt = wp.full(nworld, np.sum((mjm.eq_type == mujoco.mjtEq.mjEQ_JOINT) & mjd.eq_active), dtype=int) d.ne_jnt = wp.full(nworld, np.sum((mjm.eq_type == mujoco.mjtEq.mjEQ_JOINT) & mjd.eq_active), dtype=int)
d.ne_ten = wp.full(nworld, np.sum((mjm.eq_type == mujoco.mjtEq.mjEQ_TENDON) & mjd.eq_active), dtype=int) d.ne_ten = wp.full(nworld, np.sum((mjm.eq_type == mujoco.mjtEq.mjEQ_TENDON) & mjd.eq_active), dtype=int)
d.ne_flex = wp.full(nworld, np.sum((mjm.eq_type == mujoco.mjtEq.mjEQ_FLEX) & mjd.eq_active), dtype=int)
d.nsolving = wp.array([nworld], dtype=int) d.nsolving = wp.array([nworld], dtype=int)
return d return d
@@ -913,6 +951,7 @@ def get_data_into(
result.cdof[:] = d.cdof.numpy()[world_id] result.cdof[:] = d.cdof.numpy()[world_id]
result.cinert[:] = d.cinert.numpy()[world_id] result.cinert[:] = d.cinert.numpy()[world_id]
result.flexvert_xpos[:] = d.flexvert_xpos.numpy()[world_id] result.flexvert_xpos[:] = d.flexvert_xpos.numpy()[world_id]
result.flexedge_J[:] = d.flexedge_J.numpy()[world_id]
result.flexedge_length[:] = d.flexedge_length.numpy()[world_id] result.flexedge_length[:] = d.flexedge_length.numpy()[world_id]
result.flexedge_velocity[:] = d.flexedge_velocity.numpy()[world_id] result.flexedge_velocity[:] = d.flexedge_velocity.numpy()[world_id]
result.actuator_length[:] = d.actuator_length.numpy()[world_id] result.actuator_length[:] = d.actuator_length.numpy()[world_id]
@@ -957,12 +996,9 @@ def get_data_into(
result.contact.geom[:ncon] = d.contact.geom.numpy()[ncon_filter] result.contact.geom[:ncon] = d.contact.geom.numpy()[ncon_filter]
result.contact.efc_address[:ncon] = contact_efc_address_ordered[:ncon] result.contact.efc_address[:ncon] = contact_efc_address_ordered[:ncon]
if mujoco.mj_isSparse(mjm): if is_sparse(mjm):
result.qM[:] = d.qM.numpy()[world_id, 0] result.qM[:] = d.qM.numpy()[world_id, 0]
result.qLD[:] = d.qLD.numpy()[world_id, 0] result.qLD[:] = d.qLD.numpy()[world_id, 0]
if nefc > 0:
efc_J = d.efc.J.numpy()[world_id, efc_idx, : mjm.nv]
mujoco.mju_dense2sparse(result.efc_J, efc_J, result.efc_J_rownnz, result.efc_J_rowadr, result.efc_J_colind)
else: else:
qM = d.qM.numpy()[world_id] qM = d.qM.numpy()[world_id]
adr = 0 adr = 0
@@ -973,7 +1009,12 @@ def get_data_into(
j = mjm.dof_parentid[j] j = mjm.dof_parentid[j]
adr += 1 adr += 1
mujoco.mj_factorM(mjm, result) mujoco.mj_factorM(mjm, result)
if nefc > 0:
if nefc > 0:
if mujoco.mj_isSparse(mjm):
efc_J = d.efc.J.numpy()[world_id, efc_idx, : mjm.nv]
mujoco.mju_dense2sparse(result.efc_J, efc_J, result.efc_J_rownnz, result.efc_J_rowadr, result.efc_J_colind)
else:
result.efc_J[: nefc * mjm.nv] = d.efc.J.numpy()[world_id, :nefc, : mjm.nv].flatten() result.efc_J[: nefc * mjm.nv] = d.efc.J.numpy()[world_id, :nefc, : mjm.nv].flatten()
# efc # efc
@@ -1072,6 +1113,7 @@ def reset_data(m: types.Model, d: types.Data, reset: Optional[wp.array] = None):
ne_weld_out: wp.array(dtype=int), ne_weld_out: wp.array(dtype=int),
ne_jnt_out: wp.array(dtype=int), ne_jnt_out: wp.array(dtype=int),
ne_ten_out: wp.array(dtype=int), ne_ten_out: wp.array(dtype=int),
ne_flex_out: wp.array(dtype=int),
nsolving_out: wp.array(dtype=int), nsolving_out: wp.array(dtype=int),
): ):
worldid = wp.tid() worldid = wp.tid()
@@ -1088,6 +1130,7 @@ def reset_data(m: types.Model, d: types.Data, reset: Optional[wp.array] = None):
ne_weld_out[worldid] = 0 ne_weld_out[worldid] = 0
ne_jnt_out[worldid] = 0 ne_jnt_out[worldid] = 0
ne_ten_out[worldid] = 0 ne_ten_out[worldid] = 0
ne_flex_out[worldid] = 0
nf_out[worldid] = 0 nf_out[worldid] = 0
nl_out[worldid] = 0 nl_out[worldid] = 0
nefc_out[worldid] = 0 nefc_out[worldid] = 0
@@ -1095,8 +1138,9 @@ def reset_data(m: types.Model, d: types.Data, reset: Optional[wp.array] = None):
nsolving_out[0] = nworld_in nsolving_out[0] = nworld_in
time_out[worldid] = 0.0 time_out[worldid] = 0.0
energy_out[worldid] = wp.vec2(0.0, 0.0) energy_out[worldid] = wp.vec2(0.0, 0.0)
qpos0_id = worldid % qpos0.shape[0]
for i in range(nq): for i in range(nq):
qpos_out[worldid, i] = qpos0[worldid, i] qpos_out[worldid, i] = qpos0[qpos0_id, i]
if i < nv: if i < nv:
qvel_out[worldid, i] = 0.0 qvel_out[worldid, i] = 0.0
qacc_warmstart_out[worldid, i] = 0.0 qacc_warmstart_out[worldid, i] = 0.0
@@ -1254,6 +1298,7 @@ def reset_data(m: types.Model, d: types.Data, reset: Optional[wp.array] = None):
d.ne_weld, d.ne_weld,
d.ne_jnt, d.ne_jnt,
d.ne_ten, d.ne_ten,
d.ne_flex,
d.nsolving, d.nsolving,
], ],
) )
+38 -18
View File
@@ -551,11 +551,14 @@ def _qfrc_passive(
@wp.kernel @wp.kernel
def _flex_elasticity( def _flex_elasticity(
# Model: # Model:
nflex: int,
opt_timestep: wp.array(dtype=float), opt_timestep: wp.array(dtype=float),
body_dofadr: wp.array(dtype=int), body_dofadr: wp.array(dtype=int),
flex_dim: wp.array(dtype=int), flex_dim: wp.array(dtype=int),
flex_vertadr: wp.array(dtype=int), flex_vertadr: wp.array(dtype=int),
flex_edgeadr: wp.array(dtype=int), flex_edgeadr: wp.array(dtype=int),
flex_elemadr: wp.array(dtype=int),
flex_elemnum: wp.array(dtype=int),
flex_elemedgeadr: wp.array(dtype=int), flex_elemedgeadr: wp.array(dtype=int),
flex_vertbodyid: wp.array(dtype=int), flex_vertbodyid: wp.array(dtype=int),
flex_elem: wp.array(dtype=int), flex_elem: wp.array(dtype=int),
@@ -574,22 +577,27 @@ def _flex_elasticity(
): ):
worldid, elemid = wp.tid() worldid, elemid = wp.tid()
timestep = opt_timestep[worldid % opt_timestep.shape[0]] timestep = opt_timestep[worldid % opt_timestep.shape[0]]
f = 0 # TODO(quaglino): this should become a function of t
for i in range(nflex):
locid = elemid - flex_elemadr[i]
if locid >= 0 and locid < flex_elemnum[i]:
f = i
break
dim = flex_dim[f] dim = flex_dim[f]
nvert = dim + 1 nvert = dim + 1
nedge = nvert * (nvert - 1) / 2 nedge = nvert * (nvert - 1) / 2
edges = wp.where( edges = wp.where(
dim == 3, dim == 3,
wp.mat(0, 1, 1, 2, 2, 0, 2, 3, 0, 3, 1, 3, shape=(6, 2), dtype=int), wp.types.matrix(0, 1, 1, 2, 2, 0, 2, 3, 0, 3, 1, 3, shape=(6, 2), dtype=int),
wp.mat(1, 2, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, shape=(6, 2), dtype=int), wp.types.matrix(1, 2, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, shape=(6, 2), dtype=int),
) )
if timestep > 0.0 and not dsbl_damper: if timestep > 0.0 and not dsbl_damper:
kD = flex_damping[f] / timestep kD = flex_damping[f] / timestep
else: else:
kD = 0.0 kD = 0.0
gradient = wp.mat(0.0, shape=(6, 6)) gradient = wp.types.matrix(0.0, shape=(6, 6))
for e in range(nedge): for e in range(nedge):
vert0 = flex_elem[(dim + 1) * elemid + edges[e, 0]] vert0 = flex_elem[(dim + 1) * elemid + edges[e, 0]]
vert1 = flex_elem[(dim + 1) * elemid + edges[e, 1]] vert1 = flex_elem[(dim + 1) * elemid + edges[e, 1]]
@@ -601,14 +609,14 @@ def _flex_elasticity(
elongation = wp.spatial_vectorf(0.0) elongation = wp.spatial_vectorf(0.0)
for e in range(nedge): for e in range(nedge):
idx = flex_elemedge[flex_elemedgeadr[f] + elemid * nedge + e] idx = flex_elemedge[elemid * nedge + e]
vel = flexedge_velocity_in[worldid, flex_edgeadr[f] + idx] vel = flexedge_velocity_in[worldid, flex_edgeadr[f] + idx]
deformed = flexedge_length_in[worldid, flex_edgeadr[f] + idx] deformed = flexedge_length_in[worldid, flex_edgeadr[f] + idx]
reference = flexedge_length0[flex_edgeadr[f] + idx] reference = flexedge_length0[flex_edgeadr[f] + idx]
previous = deformed - vel * timestep previous = deformed - vel * timestep
elongation[e] = deformed * deformed - reference * reference + (deformed * deformed - previous * previous) * kD elongation[e] = deformed * deformed - reference * reference + (deformed * deformed - previous * previous) * kD
metric = wp.mat(0.0, shape=(6, 6)) metric = wp.types.matrix(0.0, shape=(6, 6))
id = int(0) id = int(0)
for ed1 in range(nedge): for ed1 in range(nedge):
for ed2 in range(ed1, nedge): for ed2 in range(ed1, nedge):
@@ -616,7 +624,7 @@ def _flex_elasticity(
metric[ed2, ed1] = flex_stiffness[elemid, id] metric[ed2, ed1] = flex_stiffness[elemid, id]
id += 1 id += 1
force = wp.mat(0.0, shape=(6, 3)) force = wp.types.matrix(0.0, shape=(6, 3))
for ed1 in range(nedge): for ed1 in range(nedge):
for ed2 in range(nedge): for ed2 in range(nedge):
for i in range(2): for i in range(2):
@@ -633,10 +641,12 @@ def _flex_elasticity(
@wp.kernel @wp.kernel
def _flex_bending( def _flex_bending(
# Model: # Model:
nflex: int,
body_dofadr: wp.array(dtype=int), body_dofadr: wp.array(dtype=int),
flex_dim: wp.array(dtype=int), flex_dim: wp.array(dtype=int),
flex_vertadr: wp.array(dtype=int), flex_vertadr: wp.array(dtype=int),
flex_edgeadr: wp.array(dtype=int), flex_edgeadr: wp.array(dtype=int),
flex_edgenum: wp.array(dtype=int),
flex_vertbodyid: wp.array(dtype=int), flex_vertbodyid: wp.array(dtype=int),
flex_edge: wp.array(dtype=wp.vec2i), flex_edge: wp.array(dtype=wp.vec2i),
flex_edgeflap: wp.array(dtype=wp.vec2i), flex_edgeflap: wp.array(dtype=wp.vec2i),
@@ -648,22 +658,27 @@ def _flex_bending(
): ):
worldid, edgeid = wp.tid() worldid, edgeid = wp.tid()
nvert = 4 nvert = 4
f = 0 # TODO(quaglino): this should become a function of t
for i in range(nflex):
locid = edgeid - flex_edgeadr[i]
if locid >= 0 and locid < flex_edgenum[i]:
f = i
break
if flex_dim[f] != 2: if flex_dim[f] != 2:
return return
v = wp.vec4i( if flex_edgeflap[edgeid][1] == -1:
flex_edge[edgeid + flex_edgeadr[f]][0],
flex_edge[edgeid + flex_edgeadr[f]][1],
flex_edgeflap[edgeid + flex_edgeadr[f]][0],
flex_edgeflap[edgeid + flex_edgeadr[f]][1],
)
if v[3] == -1:
return return
frc = wp.mat(0.0, shape=(4, 3)) v = wp.vec4i(
flex_vertadr[f] + flex_edge[edgeid][0],
flex_vertadr[f] + flex_edge[edgeid][1],
flex_vertadr[f] + flex_edgeflap[edgeid][0],
flex_vertadr[f] + flex_edgeflap[edgeid][1],
)
frc = wp.types.matrix(0.0, shape=(4, 3))
if flex_bending[edgeid, 16]: if flex_bending[edgeid, 16]:
v0 = flexvert_xpos_in[worldid, v[0]] v0 = flexvert_xpos_in[worldid, v[0]]
v1 = flexvert_xpos_in[worldid, v[1]] v1 = flexvert_xpos_in[worldid, v[1]]
@@ -674,7 +689,7 @@ def _flex_bending(
frc[3] = wp.cross(v1 - v0, v2 - v0) frc[3] = wp.cross(v1 - v0, v2 - v0)
frc[0] = -(frc[1] + frc[2] + frc[3]) frc[0] = -(frc[1] + frc[2] + frc[3])
force = wp.mat(0.0, shape=(nvert, 3)) force = wp.types.matrix(0.0, shape=(nvert, 3))
for i in range(nvert): for i in range(nvert):
for x in range(3): for x in range(3):
for j in range(nvert): for j in range(nvert):
@@ -743,11 +758,14 @@ def passive(m: Model, d: Data):
_flex_elasticity, _flex_elasticity,
dim=(d.nworld, m.nflexelem), dim=(d.nworld, m.nflexelem),
inputs=[ inputs=[
m.nflex,
m.opt.timestep, m.opt.timestep,
m.body_dofadr, m.body_dofadr,
m.flex_dim, m.flex_dim,
m.flex_vertadr, m.flex_vertadr,
m.flex_edgeadr, m.flex_edgeadr,
m.flex_elemadr,
m.flex_elemnum,
m.flex_elemedgeadr, m.flex_elemedgeadr,
m.flex_vertbodyid, m.flex_vertbodyid,
m.flex_elem, m.flex_elem,
@@ -766,10 +784,12 @@ def passive(m: Model, d: Data):
_flex_bending, _flex_bending,
dim=(d.nworld, m.nflexedge), dim=(d.nworld, m.nflexedge),
inputs=[ inputs=[
m.nflex,
m.body_dofadr, m.body_dofadr,
m.flex_dim, m.flex_dim,
m.flex_vertadr, m.flex_vertadr,
m.flex_edgeadr, m.flex_edgeadr,
m.flex_edgenum,
m.flex_vertbodyid, m.flex_vertbodyid,
m.flex_edge, m.flex_edge,
m.flex_edgeflap, m.flex_edgeflap,
+24 -21
View File
@@ -455,6 +455,7 @@ def _clock(time_in: wp.array(dtype=float), worldid: int) -> float:
@wp.kernel @wp.kernel
def _sensor_pos( def _sensor_pos(
# Model: # Model:
ngeom: int,
opt_magnetic: wp.array(dtype=wp.vec3), opt_magnetic: wp.array(dtype=wp.vec3),
body_geomnum: wp.array(dtype=int), body_geomnum: wp.array(dtype=int),
body_geomadr: wp.array(dtype=int), body_geomadr: wp.array(dtype=int),
@@ -481,10 +482,9 @@ def _sensor_pos(
sensor_refid: wp.array(dtype=int), sensor_refid: wp.array(dtype=int),
sensor_adr: wp.array(dtype=int), sensor_adr: wp.array(dtype=int),
sensor_cutoff: wp.array(dtype=float), sensor_cutoff: wp.array(dtype=float),
nxn_pairid: wp.array(dtype=wp.vec2i),
sensor_pos_adr: wp.array(dtype=int), sensor_pos_adr: wp.array(dtype=int),
rangefinder_sensor_adr: wp.array(dtype=int), rangefinder_sensor_adr: wp.array(dtype=int),
sensor_collision_start_adr: wp.array(dtype=int),
collision_sensor_adr: wp.array(dtype=int),
# Data in: # Data in:
time_in: wp.array(dtype=float), time_in: wp.array(dtype=float),
energy_in: wp.array(dtype=wp.vec2), energy_in: wp.array(dtype=wp.vec2),
@@ -605,13 +605,10 @@ def _sensor_pos(
refid = sensor_refid[sensorid] refid = sensor_refid[sensorid]
# initialize # initialize
dist = float(1.0e32) dist = float(sensor_cutoff[sensorid])
pnts = vec6(0.0, 0.0, 0.0, 0.0, 0.0, 0.0) pnts = vec6(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
flip = bool(False) flip = bool(False)
collision_sensorid = collision_sensor_adr[sensorid]
collision_start_adr = sensor_collision_start_adr[collision_sensorid]
# check for flip direction # check for flip direction
if objtype == int(ObjType.BODY.value): if objtype == int(ObjType.BODY.value):
n1 = body_geomnum[objid] n1 = body_geomnum[objid]
@@ -627,12 +624,20 @@ def _sensor_pos(
id2 = refid id2 = refid
for geom1 in range(n1): for geom1 in range(n1):
geomid1 = id1 + geom1
for geom2 in range(n2): for geom2 in range(n2):
collisionid = collision_start_adr + geom1 * n2 + geom2 geomid2 = id2 + geom2
if geomid1 <= geomid2:
pairid = math.upper_tri_index(ngeom, geomid1, geomid2)
else:
pairid = math.upper_tri_index(ngeom, geomid2, geomid1)
collisionid = nxn_pairid[pairid][1]
for i in range(8): for i in range(8):
dist_new = sensor_collision_in[worldid, collisionid, i, 0] dist_new = sensor_collision_in[worldid, collisionid, i, 0]
if dist_new <= dist: if dist_new < dist:
dist = dist_new dist = dist_new
if sensortype == SensorType.GEOMNORMAL or sensortype == SensorType.GEOMFROMTO: if sensortype == SensorType.GEOMNORMAL or sensortype == SensorType.GEOMFROMTO:
@@ -645,14 +650,12 @@ def _sensor_pos(
sensor_collision_in[worldid, collisionid, i, 6], sensor_collision_in[worldid, collisionid, i, 6],
) )
geomid1 = id1 + geom1 if geom_type[geomid1] > geom_type[geomid2]:
geomid2 = id2 + geom2 flip = True
elif geom_type[geomid1] == geom_type[geomid2]:
if geom_type[geomid2] < geom_type[geomid1]: flip = geomid1 > geomid2
flip = True else:
elif geom_type[geomid1] == geom_type[geomid2]: flip = False
if geomid2 < geomid1:
flip = True
if sensortype == int(SensorType.GEOMDIST.value): if sensortype == int(SensorType.GEOMDIST.value):
_write_scalar(sensor_type, sensor_datatype, sensor_adr, sensor_cutoff, sensorid, dist, out) _write_scalar(sensor_type, sensor_datatype, sensor_adr, sensor_cutoff, sensorid, dist, out)
elif sensortype == int(SensorType.GEOMNORMAL.value): elif sensortype == int(SensorType.GEOMNORMAL.value):
@@ -704,6 +707,7 @@ def _sensor_pos(
def _sensor_collision( def _sensor_collision(
# Model: # Model:
ngeom: int, ngeom: int,
nxn_pairid: wp.array(dtype=wp.vec2i),
# Data in: # Data in:
contact_dist_in: wp.array(dtype=float), contact_dist_in: wp.array(dtype=float),
contact_pos_in: wp.array(dtype=wp.vec3), contact_pos_in: wp.array(dtype=wp.vec3),
@@ -713,7 +717,6 @@ def _sensor_collision(
contact_type_in: wp.array(dtype=int), contact_type_in: wp.array(dtype=int),
contact_geomcollisionid_in: wp.array(dtype=int), contact_geomcollisionid_in: wp.array(dtype=int),
nacon_in: wp.array(dtype=int), nacon_in: wp.array(dtype=int),
collision_pairid_in: wp.array(dtype=wp.vec2i),
# Out: # Out:
sensor_collision_out: wp.array4d(dtype=float), sensor_collision_out: wp.array4d(dtype=float),
): ):
@@ -732,7 +735,7 @@ def _sensor_collision(
pairid = math.upper_tri_index(ngeom, geom[1], geom[0]) pairid = math.upper_tri_index(ngeom, geom[1], geom[0])
worldid = contact_worldid_in[conid] worldid = contact_worldid_in[conid]
collisionid = collision_pairid_in[pairid][1] collisionid = nxn_pairid[pairid][1]
geomcollisionid = contact_geomcollisionid_in[conid] geomcollisionid = contact_geomcollisionid_in[conid]
dist = contact_dist_in[conid] dist = contact_dist_in[conid]
@@ -800,6 +803,7 @@ def sensor_pos(m: Model, d: Data):
dim=d.naconmax, dim=d.naconmax,
inputs=[ inputs=[
m.ngeom, m.ngeom,
m.nxn_pairid,
d.contact.dist, d.contact.dist,
d.contact.pos, d.contact.pos,
d.contact.frame, d.contact.frame,
@@ -808,7 +812,6 @@ def sensor_pos(m: Model, d: Data):
d.contact.type, d.contact.type,
d.contact.geomcollisionid, d.contact.geomcollisionid,
d.nacon, d.nacon,
d.collision_pairid,
], ],
outputs=[sensor_collision], outputs=[sensor_collision],
) )
@@ -817,6 +820,7 @@ def sensor_pos(m: Model, d: Data):
_sensor_pos, _sensor_pos,
dim=(d.nworld, m.sensor_pos_adr.size), dim=(d.nworld, m.sensor_pos_adr.size),
inputs=[ inputs=[
m.ngeom,
m.opt.magnetic, m.opt.magnetic,
m.body_geomnum, m.body_geomnum,
m.body_geomadr, m.body_geomadr,
@@ -843,10 +847,9 @@ def sensor_pos(m: Model, d: Data):
m.sensor_refid, m.sensor_refid,
m.sensor_adr, m.sensor_adr,
m.sensor_cutoff, m.sensor_cutoff,
m.nxn_pairid,
m.sensor_pos_adr, m.sensor_pos_adr,
m.rangefinder_sensor_adr, m.rangefinder_sensor_adr,
m.sensor_collision_start_adr,
m.collision_sensor_adr,
d.time, d.time,
d.energy, d.energy,
d.qpos, d.qpos,
+75 -68
View File
@@ -40,28 +40,12 @@ from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False}) wp.set_module_options({"enable_backward": False})
@wp.kernel
def _kinematics_root(
# Data out:
xpos_out: wp.array2d(dtype=wp.vec3),
xquat_out: wp.array2d(dtype=wp.quat),
xmat_out: wp.array2d(dtype=wp.mat33),
xipos_out: wp.array2d(dtype=wp.vec3),
ximat_out: wp.array2d(dtype=wp.mat33),
):
worldid = wp.tid()
xpos_out[worldid, 0] = wp.vec3(0.0)
xquat_out[worldid, 0] = wp.quat(1.0, 0.0, 0.0, 0.0)
xipos_out[worldid, 0] = wp.vec3(0.0)
xmat_out[worldid, 0] = wp.identity(n=3, dtype=wp.float32)
ximat_out[worldid, 0] = wp.identity(n=3, dtype=wp.float32)
@wp.kernel @wp.kernel
def _kinematics_level( def _kinematics_level(
# Model: # Model:
qpos0: wp.array2d(dtype=float), qpos0: wp.array2d(dtype=float),
body_parentid: wp.array(dtype=int), body_parentid: wp.array(dtype=int),
body_mocapid: wp.array(dtype=int),
body_jntnum: wp.array(dtype=int), body_jntnum: wp.array(dtype=int),
body_jntadr: wp.array(dtype=int), body_jntadr: wp.array(dtype=int),
body_pos: wp.array2d(dtype=wp.vec3), body_pos: wp.array2d(dtype=wp.vec3),
@@ -74,6 +58,8 @@ def _kinematics_level(
jnt_axis: wp.array2d(dtype=wp.vec3), jnt_axis: wp.array2d(dtype=wp.vec3),
# Data in: # Data in:
qpos_in: wp.array2d(dtype=float), qpos_in: wp.array2d(dtype=float),
mocap_pos_in: wp.array2d(dtype=wp.vec3),
mocap_quat_in: wp.array2d(dtype=wp.quat),
xpos_in: wp.array2d(dtype=wp.vec3), xpos_in: wp.array2d(dtype=wp.vec3),
xquat_in: wp.array2d(dtype=wp.quat), xquat_in: wp.array2d(dtype=wp.quat),
xmat_in: wp.array2d(dtype=wp.mat33), xmat_in: wp.array2d(dtype=wp.mat33),
@@ -97,12 +83,12 @@ def _kinematics_level(
body_quat_id = worldid % body_quat.shape[0] body_quat_id = worldid % body_quat.shape[0]
jnt_axis_id = worldid % jnt_axis.shape[0] jnt_axis_id = worldid % jnt_axis.shape[0]
if jntnum == 0: free_joint = False
# no joints - apply fixed translation and rotation relative to parent if jntnum == 1:
pid = body_parentid[bodyid] jnt_type_ = jnt_type[jntadr]
xpos = (xmat_in[worldid, pid] * body_pos[body_pos_id, bodyid]) + xpos_in[worldid, pid] free_joint = jnt_type_ == JointType.FREE
xquat = math.mul_quat(xquat_in[worldid, pid], body_quat[body_quat_id, bodyid])
elif jntnum == 1 and jnt_type[jntadr] == JointType.FREE: if free_joint:
# free joint # free joint
qadr = jnt_qposadr[jntadr] qadr = jnt_qposadr[jntadr]
xpos = wp.vec3(qpos[qadr], qpos[qadr + 1], qpos[qadr + 2]) xpos = wp.vec3(qpos[qadr], qpos[qadr + 1], qpos[qadr + 2])
@@ -116,8 +102,19 @@ def _kinematics_level(
qpos0_id = worldid % qpos0.shape[0] qpos0_id = worldid % qpos0.shape[0]
jnt_pos_id = worldid % jnt_pos.shape[0] jnt_pos_id = worldid % jnt_pos.shape[0]
pid = body_parentid[bodyid] pid = body_parentid[bodyid]
xpos = (xmat_in[worldid, pid] * body_pos[body_pos_id, bodyid]) + xpos_in[worldid, pid]
xquat = math.mul_quat(xquat_in[worldid, pid], body_quat[body_quat_id, bodyid]) # mocap bodies have world body as parent
mocapid = body_mocapid[bodyid]
if mocapid >= 0:
xpos = mocap_pos_in[worldid, mocapid]
xquat = mocap_quat_in[worldid, mocapid]
else:
xpos = body_pos[body_pos_id, bodyid]
xquat = body_quat[body_quat_id, bodyid]
if pid >= 0:
xpos = xmat_in[worldid, pid] @ xpos + xpos_in[worldid, pid]
xquat = math.mul_quat(xquat_in[worldid, pid], xquat)
for _ in range(jntnum): for _ in range(jntnum):
qadr = jnt_qposadr[jntadr] qadr = jnt_qposadr[jntadr]
@@ -146,7 +143,8 @@ def _kinematics_level(
jntadr += 1 jntadr += 1
xpos_out[worldid, bodyid] = xpos xpos_out[worldid, bodyid] = xpos
xquat_out[worldid, bodyid] = wp.normalize(xquat) xquat = wp.normalize(xquat)
xquat_out[worldid, bodyid] = xquat
xmat_out[worldid, bodyid] = math.quat_to_mat(xquat) xmat_out[worldid, bodyid] = math.quat_to_mat(xquat)
xipos_out[worldid, bodyid] = xpos + math.rot_vec_quat(body_ipos[worldid % body_ipos.shape[0], bodyid], xquat) xipos_out[worldid, bodyid] = xpos + math.rot_vec_quat(body_ipos[worldid % body_ipos.shape[0], bodyid], xquat)
ximat_out[worldid, bodyid] = math.quat_to_mat(math.mul_quat(xquat, body_iquat[worldid % body_iquat.shape[0], bodyid])) ximat_out[worldid, bodyid] = math.quat_to_mat(math.mul_quat(xquat, body_iquat[worldid % body_iquat.shape[0], bodyid]))
@@ -219,19 +217,33 @@ def _flex_vertices(
@wp.kernel @wp.kernel
def _flex_edges( def _flex_edges(
# Model: # Model:
nv: int,
nflex: int,
body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int),
body_dofadr: wp.array(dtype=int), body_dofadr: wp.array(dtype=int),
dof_bodyid: wp.array(dtype=int),
flex_vertadr: wp.array(dtype=int), flex_vertadr: wp.array(dtype=int),
flex_edgeadr: wp.array(dtype=int),
flex_edgenum: wp.array(dtype=int),
flex_vertbodyid: wp.array(dtype=int), flex_vertbodyid: wp.array(dtype=int),
flex_edge: wp.array(dtype=wp.vec2i), flex_edge: wp.array(dtype=wp.vec2i),
# Data in: # Data in:
qvel_in: wp.array2d(dtype=float), qvel_in: wp.array2d(dtype=float),
subtree_com_in: wp.array2d(dtype=wp.vec3),
cdof_in: wp.array2d(dtype=wp.spatial_vector),
flexvert_xpos_in: wp.array2d(dtype=wp.vec3), flexvert_xpos_in: wp.array2d(dtype=wp.vec3),
# Data out: # Data out:
flexedge_J_out: wp.array3d(dtype=float),
flexedge_length_out: wp.array2d(dtype=float), flexedge_length_out: wp.array2d(dtype=float),
flexedge_velocity_out: wp.array2d(dtype=float), flexedge_velocity_out: wp.array2d(dtype=float),
): ):
worldid, edgeid = wp.tid() worldid, edgeid = wp.tid()
f = 0 # TODO(quaglino): get f from edgeid for i in range(nflex):
locid = edgeid - flex_edgeadr[i]
if locid >= 0 and locid < flex_edgenum[i]:
f = i
break
vbase = flex_vertadr[f] vbase = flex_vertadr[f]
v = flex_edge[edgeid] v = flex_edge[edgeid]
pos1 = flexvert_xpos_in[worldid, vbase + v[0]] pos1 = flexvert_xpos_in[worldid, vbase + v[0]]
@@ -240,38 +252,20 @@ def _flex_edges(
vecnorm = wp.length(vec) vecnorm = wp.length(vec)
flexedge_length_out[worldid, edgeid] = vecnorm flexedge_length_out[worldid, edgeid] = vecnorm
# TODO(quaglino): use Jacobian # TODO(quaglino): use Jacobian
i = body_dofadr[flex_vertbodyid[vbase + v[0]]] b1 = flex_vertbodyid[vbase + v[0]]
j = body_dofadr[flex_vertbodyid[vbase + v[1]]] b2 = flex_vertbodyid[vbase + v[1]]
i = body_dofadr[b1]
j = body_dofadr[b2]
vel1 = wp.vec3(qvel_in[worldid, i], qvel_in[worldid, i + 1], qvel_in[worldid, i + 2]) vel1 = wp.vec3(qvel_in[worldid, i], qvel_in[worldid, i + 1], qvel_in[worldid, i + 2])
vel2 = wp.vec3(qvel_in[worldid, j], qvel_in[worldid, j + 1], qvel_in[worldid, j + 2]) vel2 = wp.vec3(qvel_in[worldid, j], qvel_in[worldid, j + 1], qvel_in[worldid, j + 2])
flexedge_velocity_out[worldid, edgeid] = math.safe_div(wp.dot(vel2 - vel1, vec), vecnorm) edge = wp.normalize(vec)
flexedge_velocity_out[worldid, edgeid] = wp.dot(vel2 - vel1, edge)
# Edge jacobian
@wp.kernel for k in range(nv):
def _mocap( jacp1, _ = support.jac(body_parentid, body_rootid, dof_bodyid, subtree_com_in, cdof_in, pos1, b1, k, worldid)
# Model: jacp2, _ = support.jac(body_parentid, body_rootid, dof_bodyid, subtree_com_in, cdof_in, pos2, b2, k, worldid)
body_ipos: wp.array2d(dtype=wp.vec3), jacdif = jacp2 - jacp1
body_iquat: wp.array2d(dtype=wp.quat), flexedge_J_out[worldid, edgeid, k] = wp.dot(jacdif, edge)
mocap_bodyid: wp.array(dtype=int),
# Data in:
mocap_pos_in: wp.array2d(dtype=wp.vec3),
mocap_quat_in: wp.array2d(dtype=wp.quat),
# Data out:
xpos_out: wp.array2d(dtype=wp.vec3),
xquat_out: wp.array2d(dtype=wp.quat),
xmat_out: wp.array2d(dtype=wp.mat33),
xipos_out: wp.array2d(dtype=wp.vec3),
ximat_out: wp.array2d(dtype=wp.mat33),
):
worldid, mocapid = wp.tid()
bodyid = mocap_bodyid[mocapid]
mocap_quat = wp.normalize(mocap_quat_in[worldid, mocapid])
xpos = mocap_pos_in[worldid, mocapid]
xpos_out[worldid, bodyid] = xpos
xquat_out[worldid, bodyid] = mocap_quat
xmat_out[worldid, bodyid] = math.quat_to_mat(mocap_quat)
xipos_out[worldid, bodyid] = xpos + math.rot_vec_quat(body_ipos[worldid % body_ipos.shape[0], bodyid], mocap_quat)
ximat_out[worldid, bodyid] = math.quat_to_mat(math.mul_quat(mocap_quat, body_iquat[worldid % body_iquat.shape[0], bodyid]))
@event_scope @event_scope
@@ -282,8 +276,6 @@ def kinematics(m: Model, d: Data):
derived positions and orientations of geoms, sites, and flexible elements, based on the derived positions and orientations of geoms, sites, and flexible elements, based on the
current joint positions and any attached mocap bodies. current joint positions and any attached mocap bodies.
""" """
wp.launch(_kinematics_root, dim=(d.nworld), inputs=[], outputs=[d.xpos, d.xquat, d.xmat, d.xipos, d.ximat])
for i in range(1, len(m.body_tree)): for i in range(1, len(m.body_tree)):
body_tree = m.body_tree[i] body_tree = m.body_tree[i]
wp.launch( wp.launch(
@@ -292,6 +284,7 @@ def kinematics(m: Model, d: Data):
inputs=[ inputs=[
m.qpos0, m.qpos0,
m.body_parentid, m.body_parentid,
m.body_mocapid,
m.body_jntnum, m.body_jntnum,
m.body_jntadr, m.body_jntadr,
m.body_pos, m.body_pos,
@@ -303,6 +296,8 @@ def kinematics(m: Model, d: Data):
m.jnt_pos, m.jnt_pos,
m.jnt_axis, m.jnt_axis,
d.qpos, d.qpos,
d.mocap_pos,
d.mocap_quat,
d.xpos, d.xpos,
d.xquat, d.xquat,
d.xmat, d.xmat,
@@ -311,13 +306,6 @@ def kinematics(m: Model, d: Data):
outputs=[d.xpos, d.xquat, d.xmat, d.xipos, d.ximat, d.xanchor, d.xaxis], outputs=[d.xpos, d.xquat, d.xmat, d.xipos, d.ximat, d.xanchor, d.xaxis],
) )
wp.launch(
_mocap,
dim=(d.nworld, m.nmocap),
inputs=[m.body_ipos, m.body_iquat, m.mocap_bodyid, d.mocap_pos, d.mocap_quat],
outputs=[d.xpos, d.xquat, d.xmat, d.xipos, d.ximat],
)
wp.launch( wp.launch(
_geom_local_to_global, _geom_local_to_global,
dim=(d.nworld, m.ngeom), dim=(d.nworld, m.ngeom),
@@ -332,12 +320,31 @@ def kinematics(m: Model, d: Data):
outputs=[d.site_xpos, d.site_xmat], outputs=[d.site_xpos, d.site_xmat],
) )
@event_scope
def flex(m: Model, d: Data):
wp.launch(_flex_vertices, dim=(d.nworld, m.nflexvert), inputs=[m.flex_vertbodyid, d.xpos], outputs=[d.flexvert_xpos]) wp.launch(_flex_vertices, dim=(d.nworld, m.nflexvert), inputs=[m.flex_vertbodyid, d.xpos], outputs=[d.flexvert_xpos])
wp.launch( wp.launch(
_flex_edges, _flex_edges,
dim=(d.nworld, m.nflexedge), dim=(d.nworld, m.nflexedge),
inputs=[m.body_dofadr, m.flex_vertadr, m.flex_vertbodyid, m.flex_edge, d.qvel, d.flexvert_xpos], inputs=[
outputs=[d.flexedge_length, d.flexedge_velocity], m.nv,
m.nflex,
m.body_parentid,
m.body_rootid,
m.body_dofadr,
m.dof_bodyid,
m.flex_vertadr,
m.flex_edgeadr,
m.flex_edgenum,
m.flex_vertbodyid,
m.flex_edge,
d.qvel,
d.subtree_com,
d.cdof,
d.flexvert_xpos,
],
outputs=[d.flexedge_J, d.flexedge_length, d.flexedge_velocity],
) )
+101 -76
View File
@@ -31,10 +31,12 @@ from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False}) wp.set_module_options({"enable_backward": False})
_BLOCK_CHOLESKY_DIM = 32
@wp.func @wp.func
def _rescale(nv: int, stat_meaninertia: float, value: float) -> float: def _rescale(nv: int, stat_meaninertia: float, value: float) -> float:
return value / (stat_meaninertia * float(wp.max(1, nv))) return value / (stat_meaninertia * float(nv))
@wp.func @wp.func
@@ -291,10 +293,10 @@ def _eval(
def linesearch_iterative( def linesearch_iterative(
# Model: # Model:
nv: int, nv: int,
opt_impratio: wp.array(dtype=float),
opt_tolerance: wp.array(dtype=float), opt_tolerance: wp.array(dtype=float),
opt_ls_tolerance: wp.array(dtype=float), opt_ls_tolerance: wp.array(dtype=float),
opt_ls_iterations: int, opt_ls_iterations: int,
opt_impratio_invsqrt: wp.array(dtype=float),
stat_meaninertia: float, stat_meaninertia: float,
# Data in: # Data in:
ne_in: wp.array(dtype=int), ne_in: wp.array(dtype=int),
@@ -321,7 +323,7 @@ def linesearch_iterative(
if efc_done_in[worldid]: if efc_done_in[worldid]:
return return
impratio = opt_impratio[worldid % opt_impratio.shape[0]] impratio_invsqrt = opt_impratio_invsqrt[worldid % opt_impratio_invsqrt.shape[0]]
efc_type = efc_type_in[worldid] efc_type = efc_type_in[worldid]
efc_id = efc_id_in[worldid] efc_id = efc_id_in[worldid]
efc_D = efc_D_in[worldid] efc_D = efc_D_in[worldid]
@@ -335,11 +337,10 @@ def linesearch_iterative(
ne_clip = min(njmax_in, ne_in[worldid]) ne_clip = min(njmax_in, ne_in[worldid])
nef_clip = min(njmax_in, ne_clip + nf_in[worldid]) nef_clip = min(njmax_in, ne_clip + nf_in[worldid])
nefc_clip = min(njmax_in, nefc_in[worldid]) nefc_clip = min(njmax_in, nefc_in[worldid])
impratio_invsqrt = 1.0 / wp.sqrt(impratio)
# Calculate p0 # Calculate p0
snorm = wp.math.sqrt(efc_search_dot_in[worldid]) snorm = wp.sqrt(efc_search_dot_in[worldid])
scale = stat_meaninertia * wp.float(wp.max(1, nv)) scale = stat_meaninertia * wp.float(nv)
gtol = tolerance * ls_tolerance * snorm * scale gtol = tolerance * ls_tolerance * snorm * scale
p0 = wp.vec3(efc_quad_gauss[0], efc_quad_gauss[1], 2.0 * efc_quad_gauss[2]) p0 = wp.vec3(efc_quad_gauss[0], efc_quad_gauss[1], 2.0 * efc_quad_gauss[2])
p0 += _eval_init( p0 += _eval_init(
@@ -461,10 +462,10 @@ def _linesearch_iterative(m: types.Model, d: types.Data):
dim=d.nworld, dim=d.nworld,
inputs=[ inputs=[
m.nv, m.nv,
m.opt.impratio,
m.opt.tolerance, m.opt.tolerance,
m.opt.ls_tolerance, m.opt.ls_tolerance,
m.opt.ls_iterations, m.opt.ls_iterations,
m.opt.impratio_invsqrt,
m.stat.meaninertia, m.stat.meaninertia,
d.ne, d.ne,
d.nf, d.nf,
@@ -484,6 +485,7 @@ def _linesearch_iterative(m: types.Model, d: types.Data):
d.njmax, d.njmax,
], ],
outputs=[d.efc.alpha], outputs=[d.efc.alpha],
block_dim=m.block_dim.linesearch_iterative,
) )
@@ -496,8 +498,8 @@ def _log_scale(min_value: float, max_value: float, num_values: int, i: int) -> f
@wp.kernel @wp.kernel
def linesearch_parallel_fused( def linesearch_parallel_fused(
# Model: # Model:
opt_impratio: wp.array(dtype=float),
opt_ls_iterations: int, opt_ls_iterations: int,
opt_impratio_invsqrt: wp.array(dtype=float),
opt_ls_parallel_min_step: float, opt_ls_parallel_min_step: float,
# Data in: # Data in:
ne_in: wp.array(dtype=int), ne_in: wp.array(dtype=int),
@@ -569,7 +571,7 @@ def linesearch_parallel_fused(
continue continue
friction = contact_friction_in[conid] friction = contact_friction_in[conid]
mu = friction[0] / wp.sqrt(opt_impratio[worldid % opt_impratio.shape[0]]) mu = friction[0] * opt_impratio_invsqrt[worldid % opt_impratio_invsqrt.shape[0]]
# unpack quad # unpack quad
efcid1 = contact_efc_address_in[conid, 1] efcid1 = contact_efc_address_in[conid, 1]
@@ -649,8 +651,8 @@ def _linesearch_parallel(m: types.Model, d: types.Data, cost: wp.array2d(dtype=f
linesearch_parallel_fused, linesearch_parallel_fused,
dim=(d.nworld, m.opt.ls_iterations), dim=(d.nworld, m.opt.ls_iterations),
inputs=[ inputs=[
m.opt.impratio,
m.opt.ls_iterations, m.opt.ls_iterations,
m.opt.impratio_invsqrt,
m.opt.ls_parallel_min_step, m.opt.ls_parallel_min_step,
d.ne, d.ne,
d.nf, d.nf,
@@ -768,7 +770,7 @@ def linesearch_prepare_gauss(
@wp.kernel @wp.kernel
def linesearch_prepare_quad( def linesearch_prepare_quad(
# Model: # Model:
opt_impratio: wp.array(dtype=float), opt_impratio_invsqrt: wp.array(dtype=float),
# Data in: # Data in:
nefc_in: wp.array(dtype=int), nefc_in: wp.array(dtype=int),
contact_friction_in: wp.array(dtype=types.vec5), contact_friction_in: wp.array(dtype=types.vec5),
@@ -814,7 +816,7 @@ def linesearch_prepare_quad(
dim = contact_dim_in[conid] dim = contact_dim_in[conid]
friction = contact_friction_in[conid] friction = contact_friction_in[conid]
mu = friction[0] / wp.sqrt(opt_impratio[worldid]) mu = friction[0] * opt_impratio_invsqrt[worldid]
u0 = Jaref * mu u0 = Jaref * mu
v0 = jv * mu v0 = jv * mu
@@ -949,7 +951,7 @@ def _linesearch(m: types.Model, d: types.Data, cost: wp.array2d(dtype=float)):
linesearch_prepare_quad, linesearch_prepare_quad,
dim=(d.nworld, d.njmax), dim=(d.nworld, d.njmax),
inputs=[ inputs=[
m.opt.impratio, m.opt.impratio_invsqrt,
d.nefc, d.nefc,
d.contact.friction, d.contact.friction,
d.contact.dim, d.contact.dim,
@@ -1061,7 +1063,7 @@ def update_constraint_init_cost(
@wp.kernel @wp.kernel
def update_constraint_efc( def update_constraint_efc(
# Model: # Model:
opt_impratio: wp.array(dtype=float), opt_impratio_invsqrt: wp.array(dtype=float),
# Data in: # Data in:
ne_in: wp.array(dtype=int), ne_in: wp.array(dtype=int),
nf_in: wp.array(dtype=int), nf_in: wp.array(dtype=int),
@@ -1133,7 +1135,7 @@ def update_constraint_efc(
dim = contact_dim_in[conid] dim = contact_dim_in[conid]
friction = contact_friction_in[conid] friction = contact_friction_in[conid]
mu = friction[0] / wp.sqrt(opt_impratio[worldid]) mu = friction[0] * opt_impratio_invsqrt[worldid]
efcid0 = contact_efc_address_in[conid, 0] efcid0 = contact_efc_address_in[conid, 0]
if efcid0 < 0: if efcid0 < 0:
@@ -1261,7 +1263,7 @@ def _update_constraint(m: types.Model, d: types.Data):
update_constraint_efc, update_constraint_efc,
dim=(d.nworld, d.njmax), dim=(d.nworld, d.njmax),
inputs=[ inputs=[
m.opt.impratio, m.opt.impratio_invsqrt,
d.ne, d.ne,
d.nf, d.nf,
d.nefc, d.nefc,
@@ -1349,8 +1351,8 @@ def update_gradient_set_h_qM_lower_sparse(
# Data in: # Data in:
qM_in: wp.array3d(dtype=float), qM_in: wp.array3d(dtype=float),
efc_done_in: wp.array(dtype=bool), efc_done_in: wp.array(dtype=bool),
# Data out: # Out:
efc_h_out: wp.array3d(dtype=float), h_out: wp.array3d(dtype=float),
): ):
worldid, elementid = wp.tid() worldid, elementid = wp.tid()
@@ -1359,7 +1361,7 @@ def update_gradient_set_h_qM_lower_sparse(
i = qM_fullm_i[elementid] i = qM_fullm_i[elementid]
j = qM_fullm_j[elementid] j = qM_fullm_j[elementid]
efc_h_out[worldid, i, j] += qM_in[worldid, 0, elementid] h_out[worldid, i, j] += qM_in[worldid, 0, elementid]
@wp.func @wp.func
@@ -1390,8 +1392,8 @@ def update_gradient_JTDAJ_sparse_tiled(tile_size: int, njmax: int):
efc_D_in: wp.array2d(dtype=float), efc_D_in: wp.array2d(dtype=float),
efc_state_in: wp.array2d(dtype=int), efc_state_in: wp.array2d(dtype=int),
efc_done_in: wp.array(dtype=bool), efc_done_in: wp.array(dtype=bool),
# Data out: # Out:
efc_h_out: wp.array3d(dtype=float), h_out: wp.array3d(dtype=float),
): ):
worldid, elementid = wp.tid() worldid, elementid = wp.tid()
@@ -1442,7 +1444,7 @@ def update_gradient_JTDAJ_sparse_tiled(tile_size: int, njmax: int):
# AD: setting bounds_check to True explicitly here because for some reason it was # AD: setting bounds_check to True explicitly here because for some reason it was
# slower to disable it. # slower to disable it.
wp.tile_store(efc_h_out[worldid], sum_val, offset=(offset_i, offset_j), bounds_check=True) wp.tile_store(h_out[worldid], sum_val, offset=(offset_i, offset_j), bounds_check=True)
return kernel return kernel
@@ -1463,8 +1465,8 @@ def update_gradient_JTDAJ_dense_tiled(nv_padded: int, tile_size: int, njmax: int
efc_D_in: wp.array2d(dtype=float), efc_D_in: wp.array2d(dtype=float),
efc_state_in: wp.array2d(dtype=int), efc_state_in: wp.array2d(dtype=int),
efc_done_in: wp.array(dtype=bool), efc_done_in: wp.array(dtype=bool),
# Data out: # Out:
efc_h_out: wp.array3d(dtype=float), h_out: wp.array3d(dtype=float),
): ):
worldid = wp.tid() worldid = wp.tid()
@@ -1503,7 +1505,7 @@ def update_gradient_JTDAJ_dense_tiled(nv_padded: int, tile_size: int, njmax: int
sum_val += wp.tile_matmul(J_ki, J_kj) sum_val += wp.tile_matmul(J_ki, J_kj)
wp.tile_store(efc_h_out[worldid], sum_val, bounds_check=False) wp.tile_store(h_out[worldid], sum_val, bounds_check=False)
return kernel return kernel
@@ -1512,7 +1514,7 @@ def update_gradient_JTDAJ_dense_tiled(nv_padded: int, tile_size: int, njmax: int
@wp.kernel @wp.kernel
def update_gradient_JTCJ( def update_gradient_JTCJ(
# Model: # Model:
opt_impratio: wp.array(dtype=float), opt_impratio_invsqrt: wp.array(dtype=float),
dof_tri_row: wp.array(dtype=int), dof_tri_row: wp.array(dtype=int),
dof_tri_col: wp.array(dtype=int), dof_tri_col: wp.array(dtype=int),
# Data in: # Data in:
@@ -1532,8 +1534,8 @@ def update_gradient_JTCJ(
# In: # In:
nblocks_perblock: int, nblocks_perblock: int,
dim_block: int, dim_block: int,
# Data out: # Out:
efc_h_out: wp.array3d(dtype=float), h_out: wp.array3d(dtype=float),
): ):
conid_start, elementid = wp.tid() conid_start, elementid = wp.tid()
@@ -1564,7 +1566,7 @@ def update_gradient_JTCJ(
continue continue
fri = contact_friction_in[conid] fri = contact_friction_in[conid]
mu = math.safe_div(fri[0], wp.sqrt(opt_impratio[worldid])) mu = fri[0] * opt_impratio_invsqrt[worldid]
mu2 = mu * mu mu2 = mu * mu
dm = math.safe_div(efc_D_in[worldid, efcid0], mu2 * (1.0 + mu2)) dm = math.safe_div(efc_D_in[worldid, efcid0], mu2 * (1.0 + mu2))
@@ -1589,7 +1591,7 @@ def update_gradient_JTCJ(
t = wp.max(t, types.MJ_MINVAL) t = wp.max(t, types.MJ_MINVAL)
ttt = wp.max(t * t * t, types.MJ_MINVAL) ttt = wp.max(t * t * t, types.MJ_MINVAL)
efc_h = float(0.0) h = float(0.0)
for dim1id in range(condim): for dim1id in range(condim):
if dim1id == 0: if dim1id == 0:
@@ -1641,12 +1643,12 @@ def update_gradient_JTCJ(
hcone *= dm * fri1 * fri2 hcone *= dm * fri1 * fri2
if hcone != 0.0: if hcone != 0.0:
efc_h += hcone * efc_J11 * efc_J22 h += hcone * efc_J11 * efc_J22
if dim1id != dim2id: if dim1id != dim2id:
efc_h += hcone * efc_J12 * efc_J21 h += hcone * efc_J12 * efc_J21
efc_h_out[worldid, dof1id, dof2id] += efc_h h_out[worldid, dof1id, dof2id] += h
@cache_kernel @cache_kernel
@@ -1655,7 +1657,7 @@ def update_gradient_cholesky(tile_size: int):
def kernel( def kernel(
# Data in: # Data in:
efc_grad_in: wp.array2d(dtype=float), efc_grad_in: wp.array2d(dtype=float),
efc_h_in: wp.array3d(dtype=float), h_in: wp.array3d(dtype=float),
efc_done_in: wp.array(dtype=bool), efc_done_in: wp.array(dtype=bool),
# Data out: # Data out:
efc_Mgrad_out: wp.array2d(dtype=float), efc_Mgrad_out: wp.array2d(dtype=float),
@@ -1666,7 +1668,7 @@ def update_gradient_cholesky(tile_size: int):
if efc_done_in[worldid]: if efc_done_in[worldid]:
return return
mat_tile = wp.tile_load(efc_h_in[worldid], shape=(TILE_SIZE, TILE_SIZE)) mat_tile = wp.tile_load(h_in[worldid], shape=(TILE_SIZE, TILE_SIZE))
fact_tile = wp.tile_cholesky(mat_tile) fact_tile = wp.tile_cholesky(mat_tile)
input_tile = wp.tile_load(efc_grad_in[worldid], shape=TILE_SIZE) input_tile = wp.tile_load(efc_grad_in[worldid], shape=TILE_SIZE)
output_tile = wp.tile_cholesky_solve(fact_tile, input_tile) output_tile = wp.tile_cholesky_solve(fact_tile, input_tile)
@@ -1676,34 +1678,48 @@ def update_gradient_cholesky(tile_size: int):
@cache_kernel @cache_kernel
def update_gradient_cholesky_blocked(tile_size: int): def update_gradient_cholesky_blocked(tile_size: int, matrix_size: int):
@nested_kernel(module="unique", enable_backward=False) @nested_kernel(module="unique", enable_backward=False)
def kernel( def kernel(
# Data in: # Data in:
efc_grad_in: wp.array3d(dtype=float), efc_grad_in: wp.array3d(dtype=float),
efc_h_in: wp.array3d(dtype=float), h_in: wp.array3d(dtype=float),
efc_done_in: wp.array(dtype=bool), efc_done_in: wp.array(dtype=bool),
matrix_size: int, hfactor: wp.array3d(dtype=float),
cholesky_L_tmp: wp.array3d(dtype=float),
cholesky_y_tmp: wp.array3d(dtype=float),
# Data out: # Data out:
efc_Mgrad_out: wp.array3d(dtype=float), efc_Mgrad_out: wp.array3d(dtype=float),
): ):
worldid, tid_block = wp.tid() worldid = wp.tid()
TILE_SIZE = wp.static(tile_size) TILE_SIZE = wp.static(tile_size)
if efc_done_in[worldid]: if efc_done_in[worldid]:
return return
wp.static(create_blocked_cholesky_func(TILE_SIZE))(tid_block, efc_h_in[worldid], matrix_size, cholesky_L_tmp[worldid]) # We need matrix size both as a runtime input as well as a static input:
wp.static(create_blocked_cholesky_solve_func(TILE_SIZE))( # static input is needed to specify the tile sizes for the compiler
tid_block, cholesky_L_tmp[worldid], efc_grad_in[worldid], cholesky_y_tmp[worldid], matrix_size, efc_Mgrad_out[worldid] # runtime input is needed for the loop bounds, otherwise warp will unroll
# unconditionally leading to shared memory capacity issues.
wp.static(create_blocked_cholesky_func(TILE_SIZE))(h_in[worldid], matrix_size, hfactor[worldid])
wp.static(create_blocked_cholesky_solve_func(TILE_SIZE, matrix_size))(
hfactor[worldid], efc_grad_in[worldid], matrix_size, efc_Mgrad_out[worldid]
) )
return kernel return kernel
def _update_gradient(m: types.Model, d: types.Data): @wp.kernel
def padding_h(nv: int, efc_done_in: wp.array(dtype=bool), h_out: wp.array3d(dtype=float)):
worldid, elementid = wp.tid()
if efc_done_in[worldid]:
return
dofid = nv + elementid
h_out[worldid, dofid, dofid] = 1.0
def _update_gradient(m: types.Model, d: types.Data, h: wp.array3d(dtype=float), hfactor: wp.array3d(dtype=float)):
# grad = Ma - qfrc_smooth - qfrc_constraint # grad = Ma - qfrc_smooth - qfrc_constraint
wp.launch(update_gradient_zero_grad_dot, dim=(d.nworld), inputs=[d.efc.done], outputs=[d.efc.grad_dot]) wp.launch(update_gradient_zero_grad_dot, dim=(d.nworld), inputs=[d.efc.done], outputs=[d.efc.grad_dot])
@@ -1731,7 +1747,7 @@ def _update_gradient(m: types.Model, d: types.Data):
d.efc.state, d.efc.state,
d.efc.done, d.efc.done,
], ],
outputs=[d.efc.h], outputs=[h],
block_dim=m.block_dim.update_gradient_JTDAJ_sparse, block_dim=m.block_dim.update_gradient_JTDAJ_sparse,
) )
@@ -1739,7 +1755,7 @@ def _update_gradient(m: types.Model, d: types.Data):
update_gradient_set_h_qM_lower_sparse, update_gradient_set_h_qM_lower_sparse,
dim=(d.nworld, m.qM_fullm_i.size), dim=(d.nworld, m.qM_fullm_i.size),
inputs=[m.qM_fullm_i, m.qM_fullm_j, d.qM, d.efc.done], inputs=[m.qM_fullm_i, m.qM_fullm_j, d.qM, d.efc.done],
outputs=[d.efc.h], outputs=[h],
) )
else: else:
nv_padded = d.efc.J.shape[2] nv_padded = d.efc.J.shape[2]
@@ -1754,7 +1770,7 @@ def _update_gradient(m: types.Model, d: types.Data):
d.efc.state, d.efc.state,
d.efc.done, d.efc.done,
], ],
outputs=[d.efc.h], outputs=[h],
block_dim=m.block_dim.update_gradient_JTDAJ_dense, block_dim=m.block_dim.update_gradient_JTDAJ_dense,
) )
@@ -1786,7 +1802,7 @@ def _update_gradient(m: types.Model, d: types.Data):
update_gradient_JTCJ, update_gradient_JTCJ,
dim=(dim_block, m.dof_tri_row.size), dim=(dim_block, m.dof_tri_row.size),
inputs=[ inputs=[
m.opt.impratio, m.opt.impratio_invsqrt,
m.dof_tri_row, m.dof_tri_row,
m.dof_tri_col, m.dof_tri_col,
d.contact.dist, d.contact.dist,
@@ -1805,32 +1821,32 @@ def _update_gradient(m: types.Model, d: types.Data):
nblocks_perblock, nblocks_perblock,
dim_block, dim_block,
], ],
outputs=[d.efc.h], outputs=[h],
) )
# TODO(team): Define good threshold for blocked vs non-blocked cholesky # TODO(team): Define good threshold for blocked vs non-blocked cholesky
if m.nv < 32: if m.nv <= _BLOCK_CHOLESKY_DIM:
wp.launch_tiled( wp.launch_tiled(
update_gradient_cholesky(m.nv), update_gradient_cholesky(m.nv),
dim=d.nworld, dim=d.nworld,
inputs=[d.efc.grad, d.efc.h, d.efc.done], inputs=[d.efc.grad, h, d.efc.done],
outputs=[d.efc.Mgrad], outputs=[d.efc.Mgrad],
block_dim=m.block_dim.update_gradient_cholesky, block_dim=m.block_dim.update_gradient_cholesky,
) )
else: else:
wp.launch(
padding_h,
dim=(d.nworld, m.nv_pad - m.nv),
inputs=[m.nv, d.efc.done],
outputs=[h],
)
wp.launch_tiled( wp.launch_tiled(
update_gradient_cholesky_blocked(16), update_gradient_cholesky_blocked(types.TILE_SIZE_JTDAJ_DENSE, m.nv_pad),
dim=d.nworld, dim=d.nworld,
inputs=[ inputs=[d.efc.grad.reshape(shape=(d.nworld, d.efc.grad.shape[1], 1)), h, d.efc.done, hfactor],
d.efc.grad.reshape(shape=(d.nworld, m.nv, 1)), outputs=[d.efc.Mgrad.reshape(shape=(d.nworld, d.efc.Mgrad.shape[1], 1))],
d.efc.h, block_dim=m.block_dim.update_gradient_cholesky_blocked,
d.efc.done,
m.nv,
d.efc.cholesky_L_tmp,
d.efc.cholesky_y_tmp.reshape(shape=(d.nworld, m.nv, 1)),
],
outputs=[d.efc.Mgrad.reshape(shape=(d.nworld, m.nv, 1))],
block_dim=m.block_dim.update_gradient_cholesky,
) )
else: else:
raise ValueError(f"Unknown solver type: {m.opt.solver}") raise ValueError(f"Unknown solver type: {m.opt.solver}")
@@ -1951,7 +1967,7 @@ def solve_done(
tolerance = opt_tolerance[worldid % opt_tolerance.shape[0]] tolerance = opt_tolerance[worldid % opt_tolerance.shape[0]]
improvement = _rescale(nv, stat_meaninertia, efc_prev_cost_in[worldid] - efc_cost_in[worldid]) improvement = _rescale(nv, stat_meaninertia, efc_prev_cost_in[worldid] - efc_cost_in[worldid])
gradient = _rescale(nv, stat_meaninertia, wp.math.sqrt(efc_grad_dot_in[worldid])) gradient = _rescale(nv, stat_meaninertia, wp.sqrt(efc_grad_dot_in[worldid]))
done = (improvement < tolerance) or (gradient < tolerance) done = (improvement < tolerance) or (gradient < tolerance)
if done or solver_niter_out[worldid] == opt_iterations: if done or solver_niter_out[worldid] == opt_iterations:
# if the solver has converged or the maximum number of iterations has been reached then # if the solver has converged or the maximum number of iterations has been reached then
@@ -1964,6 +1980,8 @@ def solve_done(
def _solver_iteration( def _solver_iteration(
m: types.Model, m: types.Model,
d: types.Data, d: types.Data,
h: wp.array3d(dtype=float),
hfactor: wp.array3d(dtype=float),
step_size_cost: wp.array2d(dtype=float), step_size_cost: wp.array2d(dtype=float),
): ):
_linesearch(m, d, step_size_cost) _linesearch(m, d, step_size_cost)
@@ -1977,7 +1995,7 @@ def _solver_iteration(
) )
_update_constraint(m, d) _update_constraint(m, d)
_update_gradient(m, d) _update_gradient(m, d, h, hfactor)
# polak-ribiere # polak-ribiere
if m.opt.solver == types.SolverType.CG: if m.opt.solver == types.SolverType.CG:
@@ -2014,7 +2032,9 @@ def _solver_iteration(
) )
def create_context(m: types.Model, d: types.Data, grad: bool = True): def create_context(
m: types.Model, d: types.Data, h: wp.array3d(dtype=float), hfactor: wp.array3d(dtype=float), grad: bool = True
):
# initialize some efc arrays # initialize some efc arrays
wp.launch( wp.launch(
solve_init_efc, solve_init_efc,
@@ -2036,7 +2056,7 @@ def create_context(m: types.Model, d: types.Data, grad: bool = True):
_update_constraint(m, d) _update_constraint(m, d)
if grad: if grad:
_update_gradient(m, d) _update_gradient(m, d, h, hfactor)
@event_scope @event_scope
@@ -2055,8 +2075,19 @@ def _solve(m: types.Model, d: types.Data):
else: else:
wp.copy(d.qacc, d.qacc_smooth) wp.copy(d.qacc, d.qacc_smooth)
# Newton solver Hessian
if m.opt.solver == types.SolverType.NEWTON:
h = wp.zeros((d.nworld, m.nv_pad, m.nv_pad), dtype=float)
if m.nv > _BLOCK_CHOLESKY_DIM:
hfactor = wp.zeros((d.nworld, m.nv_pad, m.nv_pad), dtype=float)
else:
hfactor = wp.empty((d.nworld, 0, 0), dtype=float)
else:
h = wp.empty((d.nworld, 0, 0), dtype=float)
hfactor = wp.empty((d.nworld, 0, 0), dtype=float)
# create context # create context
create_context(m, d, grad=True) create_context(m, d, h, hfactor, grad=True)
# search = -Mgrad # search = -Mgrad
wp.launch( wp.launch(
@@ -2077,16 +2108,10 @@ def _solve(m: types.Model, d: types.Data):
# becomes zero and all worlds are marked as converged to avoid an infinite loop. # becomes zero and all worlds are marked as converged to avoid an infinite loop.
# note: we only launch the iteration kernel if everything is not done # note: we only launch the iteration kernel if everything is not done
d.nsolving.fill_(d.nworld) d.nsolving.fill_(d.nworld)
wp.capture_while( wp.capture_while(d.nsolving, while_body=_solver_iteration, m=m, d=d, h=h, hfactor=hfactor, step_size_cost=step_size_cost)
d.nsolving,
while_body=_solver_iteration,
m=m,
d=d,
step_size_cost=step_size_cost,
)
else: else:
# This branch is mostly for when JAX is used as it is currently not compatible # This branch is mostly for when JAX is used as it is currently not compatible
# with CUDA graph conditional. # with CUDA graph conditional.
# It should be removed when JAX becomes compatible. # It should be removed when JAX becomes compatible.
for _ in range(m.opt.iterations): for _ in range(m.opt.iterations):
_solver_iteration(m, d, step_size_cost) _solver_iteration(m, d, h, hfactor, step_size_cost)
+3 -3
View File
@@ -116,10 +116,10 @@ def mul_m_dense(tile: TileSet, check_skip: bool):
return return
dofid = adr[nodeid] dofid = adr[nodeid]
qM_tile = wp.tile_load(qM_in[worldid], shape=(TILE_SIZE, TILE_SIZE), offset=(dofid, dofid)) qM_tile = wp.tile_load(qM_in[worldid], shape=(TILE_SIZE, TILE_SIZE), offset=(dofid, dofid), bounds_check=False)
vec_tile = wp.tile_load(vec[worldid], shape=(TILE_SIZE, 1), offset=(dofid, 0)) vec_tile = wp.tile_load(vec[worldid], shape=(TILE_SIZE, 1), offset=(dofid, 0), bounds_check=False)
res_tile = wp.tile_matmul(qM_tile, vec_tile) res_tile = wp.tile_matmul(qM_tile, vec_tile)
wp.tile_store(res[worldid], res_tile, offset=(dofid, 0)) wp.tile_store(res[worldid], res_tile, offset=(dofid, 0), bounds_check=False)
return _mul_m_dense return _mul_m_dense
+27 -13
View File
@@ -58,8 +58,10 @@ class BlockDim:
cholesky_factorize_solve: int = 32 cholesky_factorize_solve: int = 32
# solver # solver
update_gradient_cholesky: int = 64 update_gradient_cholesky: int = 64
update_gradient_cholesky_blocked: int = 32
update_gradient_JTDAJ_sparse: int = 64 update_gradient_JTDAJ_sparse: int = 64
update_gradient_JTDAJ_dense: int = 96 update_gradient_JTDAJ_dense: int = 96
linesearch_iterative: int = 64
# support # support
mul_m_dense: int = 32 mul_m_dense: int = 32
@@ -502,13 +504,15 @@ class EqType(enum.IntEnum):
JOINT: couple the values of two scalar joints with cubic JOINT: couple the values of two scalar joints with cubic
WELD: fix relative position and orientation of two bodies WELD: fix relative position and orientation of two bodies
TENDON: couple the lengths of two tendons with cubic TENDON: couple the lengths of two tendons with cubic
FLEX: couple the edge lengths of a flex
""" """
CONNECT = mujoco.mjtEq.mjEQ_CONNECT CONNECT = mujoco.mjtEq.mjEQ_CONNECT
WELD = mujoco.mjtEq.mjEQ_WELD WELD = mujoco.mjtEq.mjEQ_WELD
JOINT = mujoco.mjtEq.mjEQ_JOINT JOINT = mujoco.mjtEq.mjEQ_JOINT
TENDON = mujoco.mjtEq.mjEQ_TENDON TENDON = mujoco.mjtEq.mjEQ_TENDON
# unsupported: FLEX, DISTANCE FLEX = mujoco.mjtEq.mjEQ_FLEX
# unsupported: DISTANCE
class WrapType(enum.IntEnum): class WrapType(enum.IntEnum):
@@ -638,7 +642,6 @@ class Option:
Attributes: Attributes:
timestep: simulation timestep timestep: simulation timestep
impratio: ratio of friction-to-normal contact impedance
tolerance: main solver tolerance tolerance: main solver tolerance
ls_tolerance: CG/Newton linesearch tolerance ls_tolerance: CG/Newton linesearch tolerance
ccd_tolerance: convex collision detection tolerance ccd_tolerance: convex collision detection tolerance
@@ -659,6 +662,7 @@ class Option:
sdf_iterations: max number of iterations for gradient descent sdf_iterations: max number of iterations for gradient descent
warp only fields: warp only fields:
impratio_invsqrt: ratio of friction-to-normal contact impedance (stored as inverse square root)
is_sparse: whether to use sparse representations is_sparse: whether to use sparse representations
ls_parallel: evaluate engine solver step sizes in parallel ls_parallel: evaluate engine solver step sizes in parallel
ls_parallel_min_step: minimum step size for solver linesearch ls_parallel_min_step: minimum step size for solver linesearch
@@ -674,7 +678,6 @@ class Option:
""" """
timestep: array("*", float) timestep: array("*", float)
impratio: array("*", float)
tolerance: array("*", float) tolerance: array("*", float)
ls_tolerance: array("*", float) ls_tolerance: array("*", float)
ccd_tolerance: array("*", float) ccd_tolerance: array("*", float)
@@ -694,6 +697,7 @@ class Option:
sdf_initpoints: int sdf_initpoints: int
sdf_iterations: int sdf_iterations: int
# warp only fields: # warp only fields:
impratio_invsqrt: array("*", float)
is_sparse: bool is_sparse: bool
ls_parallel: bool ls_parallel: bool
ls_parallel_min_step: float ls_parallel_min_step: float
@@ -883,6 +887,9 @@ class Model:
flex_vertadr: first vertex address (nflex,) flex_vertadr: first vertex address (nflex,)
flex_vertnum: number of vertices (nflex,) flex_vertnum: number of vertices (nflex,)
flex_edgeadr: first edge address (nflex,) flex_edgeadr: first edge address (nflex,)
flex_edgenum: number of edges (nflex,)
flex_elemadr: first element address (nflex,)
flex_elemnum: number of elements (nflex,)
flex_elemedgeadr: first element address (nflex,) flex_elemedgeadr: first element address (nflex,)
flex_vertbodyid: vertex body ids (nflexvert,) flex_vertbodyid: vertex body ids (nflexvert,)
flex_edge: edge vertex ids (2 per edge) (nflexedge, 2) flex_edge: edge vertex ids (2 per edge) (nflexedge, 2)
@@ -890,6 +897,7 @@ class Model:
flex_elem: element vertex ids (dim+1 per elem) (nflexelemdata,) flex_elem: element vertex ids (dim+1 per elem) (nflexelemdata,)
flex_elemedge: element edge ids (nflexelemedge,) flex_elemedge: element edge ids (nflexelemedge,)
flexedge_length0: edge lengths in qpos0 (nflexedge,) flexedge_length0: edge lengths in qpos0 (nflexedge,)
flexedge_invweight0: inv. inertia for the edge (nflexedge,)
flex_stiffness: finite element stiffness matrix (nflexelem, 21) flex_stiffness: finite element stiffness matrix (nflexelem, 21)
flex_bending: bending stiffness (nflexedge, 17) flex_bending: bending stiffness (nflexedge, 17)
flex_damping: Rayleigh's damping coefficient (nflex,) flex_damping: Rayleigh's damping coefficient (nflex,)
@@ -998,6 +1006,7 @@ class Model:
mapM2M: index mapping from M (legacy) to M (CSR) (nC) mapM2M: index mapping from M (legacy) to M (CSR) (nC)
warp only fields: warp only fields:
nv_pad: number of degrees of freedom + padding
nacttrnbody: number of actuators with body transmission nacttrnbody: number of actuators with body transmission
nsensorcollision: number of unique collisions for nsensorcollision: number of unique collisions for
geom distance sensors geom distance sensors
@@ -1031,6 +1040,7 @@ class Model:
eq_wld_adr: eq_* addresses of type `WELD` eq_wld_adr: eq_* addresses of type `WELD`
eq_jnt_adr: eq_* addresses of type `JOINT` eq_jnt_adr: eq_* addresses of type `JOINT`
eq_ten_adr: eq_* addresses of type `TENDON` eq_ten_adr: eq_* addresses of type `TENDON`
eq_flex_adr: eq * addresses of type `FLEX
tendon_jnt_adr: joint tendon address tendon_jnt_adr: joint tendon address
tendon_site_pair_adr: site pair tendon address tendon_site_pair_adr: site pair tendon address
tendon_geom_adr: geom tendon address tendon_geom_adr: geom tendon address
@@ -1225,6 +1235,9 @@ class Model:
flex_vertadr: array("nflex", int) flex_vertadr: array("nflex", int)
flex_vertnum: array("nflex", int) flex_vertnum: array("nflex", int)
flex_edgeadr: array("nflex", int) flex_edgeadr: array("nflex", int)
flex_edgenum: array("nflex", int)
flex_elemadr: array("nflex", int)
flex_elemnum: array("nflex", int)
flex_elemedgeadr: array("nflex", int) flex_elemedgeadr: array("nflex", int)
flex_vertbodyid: array("nflexvert", int) flex_vertbodyid: array("nflexvert", int)
flex_edge: array("nflexedge", wp.vec2i) flex_edge: array("nflexedge", wp.vec2i)
@@ -1232,6 +1245,7 @@ class Model:
flex_elem: array("nflexelemdata", int) flex_elem: array("nflexelemdata", int)
flex_elemedge: array("nflexelemedge", int) flex_elemedge: array("nflexelemedge", int)
flexedge_length0: array("nflexedge", float) flexedge_length0: array("nflexedge", float)
flexedge_invweight0: array("nflexedge", float)
flex_stiffness: array("nflexelem", 21, float) flex_stiffness: array("nflexelem", 21, float)
flex_bending: array("nflexedge", 17, float) flex_bending: array("nflexedge", 17, float)
flex_damping: array("nflex", float) flex_damping: array("nflex", float)
@@ -1339,6 +1353,7 @@ class Model:
M_colind: array("nC", int) M_colind: array("nC", int)
mapM2M: array("nC", int) mapM2M: array("nC", int)
# warp only fields: # warp only fields:
nv_pad: int
nacttrnbody: int nacttrnbody: int
nsensorcollision: int nsensorcollision: int
nsensortaxel: int nsensortaxel: int
@@ -1367,6 +1382,7 @@ class Model:
eq_wld_adr: wp.array(dtype=int) eq_wld_adr: wp.array(dtype=int)
eq_jnt_adr: wp.array(dtype=int) eq_jnt_adr: wp.array(dtype=int)
eq_ten_adr: wp.array(dtype=int) eq_ten_adr: wp.array(dtype=int)
eq_flex_adr: wp.array(dtype=int)
tendon_jnt_adr: wp.array(dtype=int) tendon_jnt_adr: wp.array(dtype=int)
tendon_site_pair_adr: wp.array(dtype=int) tendon_site_pair_adr: wp.array(dtype=int)
tendon_geom_adr: wp.array(dtype=int) tendon_geom_adr: wp.array(dtype=int)
@@ -1476,11 +1492,9 @@ class Constraint:
force: constraint force in constraint space (nworld, njmax) force: constraint force in constraint space (nworld, njmax)
Jaref: Jac*qacc - aref (nworld, njmax) Jaref: Jac*qacc - aref (nworld, njmax)
Ma: M*qacc (nworld, nv) Ma: M*qacc (nworld, nv)
grad: gradient of master cost (nworld, nv) grad: gradient of master cost (nworld, nv_pad)
cholesky_L_tmp: temporary for Cholesky factor (nworld, nv, nv)
cholesky_y_tmp: temporary for Cholesky solve (nworld, nv
grad_dot: dot(grad, grad) (nworld,) grad_dot: dot(grad, grad) (nworld,)
Mgrad: M / grad (nworld, nv) Mgrad: M / grad (nworld, nv_pad)
search: linesearch vector (nworld, nv) search: linesearch vector (nworld, nv)
search_dot: dot(search, search) (nworld,) search_dot: dot(search, search) (nworld,)
gauss: Gauss Cost (nworld,) gauss: Gauss Cost (nworld,)
@@ -1491,7 +1505,6 @@ class Constraint:
jv: efc_J @ search (nworld, njmax) jv: efc_J @ search (nworld, njmax)
quad: quadratic cost coefficients (nworld, njmax, 3) quad: quadratic cost coefficients (nworld, njmax, 3)
quad_gauss: quadratic cost Gauss coefficients (nworld, 3) quad_gauss: quadratic cost Gauss coefficients (nworld, 3)
h: Hessian (nworld, nv_pad, nv_pad)
alpha: line search step size (nworld,) alpha: line search step size (nworld,)
prev_grad: previous grad (nworld, nv) prev_grad: previous grad (nworld, nv)
prev_Mgrad: previous Mgrad (nworld, nv) prev_Mgrad: previous Mgrad (nworld, nv)
@@ -1511,11 +1524,9 @@ class Constraint:
force: array("nworld", "njmax", float) force: array("nworld", "njmax", float)
Jaref: array("nworld", "njmax", float) Jaref: array("nworld", "njmax", float)
Ma: array("nworld", "nv", float) Ma: array("nworld", "nv", float)
grad: array("nworld", "nv", float) grad: array("nworld", "nv_pad", float)
cholesky_L_tmp: array("nworld", "nv", "nv", float)
cholesky_y_tmp: array("nworld", "nv", float)
grad_dot: array("nworld", float) grad_dot: array("nworld", float)
Mgrad: array("nworld", "nv", float) Mgrad: array("nworld", "nv_pad", float)
search: array("nworld", "nv", float) search: array("nworld", "nv", float)
search_dot: array("nworld", float) search_dot: array("nworld", float)
gauss: array("nworld", float) gauss: array("nworld", float)
@@ -1526,7 +1537,6 @@ class Constraint:
jv: array("nworld", "njmax", float) jv: array("nworld", "njmax", float)
quad: array("nworld", "njmax", wp.vec3) quad: array("nworld", "njmax", wp.vec3)
quad_gauss: array("nworld", wp.vec3) quad_gauss: array("nworld", wp.vec3)
h: array("nworld", "nv_pad", "nv_pad", float)
alpha: array("nworld", float) alpha: array("nworld", float)
prev_grad: array("nworld", "nv", float) prev_grad: array("nworld", "nv", float)
prev_Mgrad: array("nworld", "nv", float) prev_Mgrad: array("nworld", "nv", float)
@@ -1578,6 +1588,7 @@ class Data:
cdof: com-based motion axis of each dof (rot:lin) (nworld, nv, 6) cdof: com-based motion axis of each dof (rot:lin) (nworld, nv, 6)
cinert: com-based body inertia and mass (nworld, nbody, 10) cinert: com-based body inertia and mass (nworld, nbody, 10)
flexvert_xpos: cartesian flex vertex positions (nworld, nflexvert, 3) flexvert_xpos: cartesian flex vertex positions (nworld, nflexvert, 3)
flexedge_J: edge length Jacobian (nworld, nflexedge, nv)
flexedge_length: flex edge lengths (nworld, nflexedge, 1) flexedge_length: flex edge lengths (nworld, nflexedge, 1)
ten_wrapadr: start address of tendon's path (nworld, ntendon) ten_wrapadr: start address of tendon's path (nworld, ntendon)
ten_wrapnum: number of wrap points in path (nworld, ntendon) ten_wrapnum: number of wrap points in path (nworld, ntendon)
@@ -1629,6 +1640,7 @@ class Data:
ne_weld: number of equality weld constraints (nworld,) ne_weld: number of equality weld constraints (nworld,)
ne_jnt: number of equality joint constraints (nworld,) ne_jnt: number of equality joint constraints (nworld,)
ne_ten: number of equality tendon constraints (nworld,) ne_ten: number of equality tendon constraints (nworld,)
ne_flex: number of flex edge equality constraints (nworld,)
nsolving: number of unconverged worlds (1,) nsolving: number of unconverged worlds (1,)
subtree_bodyvel: subtree body velocity (ang, vel) (nworld, nbody, 6) subtree_bodyvel: subtree body velocity (ang, vel) (nworld, nbody, 6)
collision_pair: collision pairs from broadphase (naconmax, 2) collision_pair: collision pairs from broadphase (naconmax, 2)
@@ -1676,6 +1688,7 @@ class Data:
cdof: array("nworld", "nv", wp.spatial_vector) cdof: array("nworld", "nv", wp.spatial_vector)
cinert: array("nworld", "nbody", vec10) cinert: array("nworld", "nbody", vec10)
flexvert_xpos: array("nworld", "nflexvert", wp.vec3) flexvert_xpos: array("nworld", "nflexvert", wp.vec3)
flexedge_J: array("nworld", "nflexedge", "nv", float)
flexedge_length: array("nworld", "nflexedge", float) flexedge_length: array("nworld", "nflexedge", float)
ten_wrapadr: array("nworld", "ntendon", int) ten_wrapadr: array("nworld", "ntendon", int)
ten_wrapnum: array("nworld", "ntendon", int) ten_wrapnum: array("nworld", "ntendon", int)
@@ -1723,6 +1736,7 @@ class Data:
ne_weld: array("nworld", int) ne_weld: array("nworld", int)
ne_jnt: array("nworld", int) ne_jnt: array("nworld", int)
ne_ten: array("nworld", int) ne_ten: array("nworld", int)
ne_flex: array("nworld", int)
nsolving: array(1, int) nsolving: array(1, int)
subtree_bodyvel: array("nworld", "nbody", wp.spatial_vector) subtree_bodyvel: array("nworld", "nbody", wp.spatial_vector)
+5 -5
View File
@@ -18,8 +18,8 @@ import inspect
from typing import Callable, Optional from typing import Callable, Optional
import warp as wp import warp as wp
from warp.context import Module from warp._src.context import Module
from warp.context import get_module from warp._src.context import get_module
_STACK = None _STACK = None
@@ -220,8 +220,8 @@ def cache_kernel(func):
def check_toolkit_driver(): def check_toolkit_driver():
if wp.context.runtime is None: if wp._src.context.runtime is None:
wp.context.init() wp._src.context.init()
if wp.get_device().is_cuda: if wp.get_device().is_cuda:
if wp.context.runtime.toolkit_version < (12, 4) or wp.context.runtime.driver_version < (12, 4): if wp._src.context.runtime.toolkit_version < (12, 4) or wp._src.context.runtime.driver_version < (12, 4):
RuntimeError("Minimum supported CUDA version: 12.4.") RuntimeError("Minimum supported CUDA version: 12.4.")
+4 -3
View File
@@ -18,13 +18,12 @@ classifiers = [
"Intended Audience :: Science/Research", "Intended Audience :: Science/Research",
"Natural Language :: English", "Natural Language :: English",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering",
] ]
requires-python = ">=3.9" requires-python = ">=3.10"
dependencies = [ dependencies = [
"absl-py", "absl-py",
"etils[epath]", "etils[epath]",
@@ -35,7 +34,7 @@ dependencies = [
[[tool.uv.index]] [[tool.uv.index]]
name = "nvidia" name = "nvidia"
url = "https://pypi.nvidia.com" url = "https://pypi.nvidia.com/"
explicit = true explicit = true
[[tool.uv.index]] [[tool.uv.index]]
@@ -56,6 +55,8 @@ dev = [
"ruff", "ruff",
"pygls>=1.0.0,<2.0.0", "pygls>=1.0.0,<2.0.0",
"lsprotocol>=2023.0.1,<2024.0.0", "lsprotocol>=2023.0.1,<2024.0.0",
"mujoco>=3.3.7.dev0",
"warp-lang>=1.9.1.dev0",
] ]
# TODO(team): cpu and cuda JAX optional dependencies are temporary, remove after we land MJX:Warp # TODO(team): cpu and cuda JAX optional dependencies are temporary, remove after we land MJX:Warp
cpu = [ cpu = [
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -101,6 +101,7 @@ class ForwardTest(parameterized.TestCase):
m = test_util.load_test_file(xml) m = test_util.load_test_file(xml)
m.opt.iterations = 10 m.opt.iterations = 10
m.opt.ls_iterations = 10 m.opt.ls_iterations = 10
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_DENSE
mx = mjx.put_model(m, impl='warp') mx = mjx.put_model(m, impl='warp')
d = mujoco.MjData(m) d = mujoco.MjData(m)
@@ -150,11 +151,8 @@ class ForwardTest(parameterized.TestCase):
tu.assert_attr_eq(dx._impl, d, 'wrap_obj') tu.assert_attr_eq(dx._impl, d, 'wrap_obj')
tu.assert_attr_eq(dx._impl, d, 'crb') tu.assert_attr_eq(dx._impl, d, 'crb')
if not mx.opt._impl.is_sparse: qm = np.zeros((m.nv, m.nv))
qm = np.zeros((m.nv, m.nv)) mujoco.mj_fullM(m, qm, d.qM)
mujoco.mj_fullM(m, qm, d.qM)
else:
qm = d.qM
# mjwarp adds padding to qM # mjwarp adds padding to qM
tu.assert_eq(qm, dx._impl.qM[: m.nv, : m.nv], 'qM') tu.assert_eq(qm, dx._impl.qM[: m.nv, : m.nv], 'qM')
# qLD is fused in a cholesky factorize and solve, and not written to. # qLD is fused in a cholesky factorize and solve, and not written to.
+15 -63
View File
@@ -46,7 +46,6 @@ _e = mjwarp.Constraint(
def _kinematics_shim( def _kinematics_shim(
# Model # Model
nworld: int, nworld: int,
body_dofadr: wp.array(dtype=int),
body_ipos: wp.array2d(dtype=wp.vec3), body_ipos: wp.array2d(dtype=wp.vec3),
body_iquat: wp.array2d(dtype=wp.quat), body_iquat: wp.array2d(dtype=wp.quat),
body_jntadr: wp.array(dtype=int), body_jntadr: wp.array(dtype=int),
@@ -58,9 +57,6 @@ def _kinematics_shim(
body_rootid: wp.array(dtype=int), body_rootid: wp.array(dtype=int),
body_tree: tuple[wp.array(dtype=int), ...], body_tree: tuple[wp.array(dtype=int), ...],
body_weldid: wp.array(dtype=int), body_weldid: wp.array(dtype=int),
flex_edge: wp.array(dtype=wp.vec2i),
flex_vertadr: wp.array(dtype=int),
flex_vertbodyid: wp.array(dtype=int),
geom_bodyid: wp.array(dtype=int), geom_bodyid: wp.array(dtype=int),
geom_pos: wp.array2d(dtype=wp.vec3), geom_pos: wp.array2d(dtype=wp.vec3),
geom_quat: wp.array2d(dtype=wp.quat), geom_quat: wp.array2d(dtype=wp.quat),
@@ -68,26 +64,18 @@ def _kinematics_shim(
jnt_pos: wp.array2d(dtype=wp.vec3), jnt_pos: wp.array2d(dtype=wp.vec3),
jnt_qposadr: wp.array(dtype=int), jnt_qposadr: wp.array(dtype=int),
jnt_type: wp.array(dtype=int), jnt_type: wp.array(dtype=int),
mocap_bodyid: wp.array(dtype=int),
nflexedge: int,
nflexvert: int,
ngeom: int, ngeom: int,
nmocap: int,
nsite: int, nsite: int,
qpos0: wp.array2d(dtype=float), qpos0: wp.array2d(dtype=float),
site_bodyid: wp.array(dtype=int), site_bodyid: wp.array(dtype=int),
site_pos: wp.array2d(dtype=wp.vec3), site_pos: wp.array2d(dtype=wp.vec3),
site_quat: wp.array2d(dtype=wp.quat), site_quat: wp.array2d(dtype=wp.quat),
# Data # Data
flexedge_length: wp.array2d(dtype=float),
flexedge_velocity: wp.array2d(dtype=float),
flexvert_xpos: wp.array2d(dtype=wp.vec3),
geom_xmat: wp.array2d(dtype=wp.mat33), geom_xmat: wp.array2d(dtype=wp.mat33),
geom_xpos: wp.array2d(dtype=wp.vec3), geom_xpos: wp.array2d(dtype=wp.vec3),
mocap_pos: wp.array2d(dtype=wp.vec3), mocap_pos: wp.array2d(dtype=wp.vec3),
mocap_quat: wp.array2d(dtype=wp.quat), mocap_quat: wp.array2d(dtype=wp.quat),
qpos: wp.array2d(dtype=float), qpos: wp.array2d(dtype=float),
qvel: wp.array2d(dtype=float),
site_xmat: wp.array2d(dtype=wp.mat33), site_xmat: wp.array2d(dtype=wp.mat33),
site_xpos: wp.array2d(dtype=wp.vec3), site_xpos: wp.array2d(dtype=wp.vec3),
xanchor: wp.array2d(dtype=wp.vec3), xanchor: wp.array2d(dtype=wp.vec3),
@@ -102,7 +90,6 @@ def _kinematics_shim(
_m.opt = _o _m.opt = _o
_d.efc = _e _d.efc = _e
_d.contact = _c _d.contact = _c
_m.body_dofadr = body_dofadr
_m.body_ipos = body_ipos _m.body_ipos = body_ipos
_m.body_iquat = body_iquat _m.body_iquat = body_iquat
_m.body_jntadr = body_jntadr _m.body_jntadr = body_jntadr
@@ -114,9 +101,6 @@ def _kinematics_shim(
_m.body_rootid = body_rootid _m.body_rootid = body_rootid
_m.body_tree = body_tree _m.body_tree = body_tree
_m.body_weldid = body_weldid _m.body_weldid = body_weldid
_m.flex_edge = flex_edge
_m.flex_vertadr = flex_vertadr
_m.flex_vertbodyid = flex_vertbodyid
_m.geom_bodyid = geom_bodyid _m.geom_bodyid = geom_bodyid
_m.geom_pos = geom_pos _m.geom_pos = geom_pos
_m.geom_quat = geom_quat _m.geom_quat = geom_quat
@@ -124,25 +108,17 @@ def _kinematics_shim(
_m.jnt_pos = jnt_pos _m.jnt_pos = jnt_pos
_m.jnt_qposadr = jnt_qposadr _m.jnt_qposadr = jnt_qposadr
_m.jnt_type = jnt_type _m.jnt_type = jnt_type
_m.mocap_bodyid = mocap_bodyid
_m.nflexedge = nflexedge
_m.nflexvert = nflexvert
_m.ngeom = ngeom _m.ngeom = ngeom
_m.nmocap = nmocap
_m.nsite = nsite _m.nsite = nsite
_m.qpos0 = qpos0 _m.qpos0 = qpos0
_m.site_bodyid = site_bodyid _m.site_bodyid = site_bodyid
_m.site_pos = site_pos _m.site_pos = site_pos
_m.site_quat = site_quat _m.site_quat = site_quat
_d.flexedge_length = flexedge_length
_d.flexedge_velocity = flexedge_velocity
_d.flexvert_xpos = flexvert_xpos
_d.geom_xmat = geom_xmat _d.geom_xmat = geom_xmat
_d.geom_xpos = geom_xpos _d.geom_xpos = geom_xpos
_d.mocap_pos = mocap_pos _d.mocap_pos = mocap_pos
_d.mocap_quat = mocap_quat _d.mocap_quat = mocap_quat
_d.qpos = qpos _d.qpos = qpos
_d.qvel = qvel
_d.site_xmat = site_xmat _d.site_xmat = site_xmat
_d.site_xpos = site_xpos _d.site_xpos = site_xpos
_d.xanchor = xanchor _d.xanchor = xanchor
@@ -158,15 +134,11 @@ def _kinematics_shim(
def _kinematics_jax_impl(m: types.Model, d: types.Data): def _kinematics_jax_impl(m: types.Model, d: types.Data):
output_dims = { output_dims = {
'flexedge_length': d._impl.flexedge_length.shape,
'flexedge_velocity': d._impl.flexedge_velocity.shape,
'flexvert_xpos': d._impl.flexvert_xpos.shape,
'geom_xmat': d.geom_xmat.shape, 'geom_xmat': d.geom_xmat.shape,
'geom_xpos': d.geom_xpos.shape, 'geom_xpos': d.geom_xpos.shape,
'mocap_pos': d.mocap_pos.shape, 'mocap_pos': d.mocap_pos.shape,
'mocap_quat': d.mocap_quat.shape, 'mocap_quat': d.mocap_quat.shape,
'qpos': d.qpos.shape, 'qpos': d.qpos.shape,
'qvel': d.qvel.shape,
'site_xmat': d.site_xmat.shape, 'site_xmat': d.site_xmat.shape,
'site_xpos': d.site_xpos.shape, 'site_xpos': d.site_xpos.shape,
'xanchor': d.xanchor.shape, 'xanchor': d.xanchor.shape,
@@ -179,19 +151,15 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
} }
jf = ffi.jax_callable_variadic_tuple( jf = ffi.jax_callable_variadic_tuple(
_kinematics_shim, _kinematics_shim,
num_outputs=18, num_outputs=14,
output_dims=output_dims, output_dims=output_dims,
vmap_method=None, vmap_method=None,
in_out_argnames={ in_out_argnames={
'flexedge_length',
'flexedge_velocity',
'flexvert_xpos',
'geom_xmat', 'geom_xmat',
'geom_xpos', 'geom_xpos',
'mocap_pos', 'mocap_pos',
'mocap_quat', 'mocap_quat',
'qpos', 'qpos',
'qvel',
'site_xmat', 'site_xmat',
'site_xpos', 'site_xpos',
'xanchor', 'xanchor',
@@ -205,7 +173,6 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
) )
out = jf( out = jf(
d.qpos.shape[0], d.qpos.shape[0],
m.body_dofadr,
m.body_ipos, m.body_ipos,
m.body_iquat, m.body_iquat,
m.body_jntadr, m.body_jntadr,
@@ -217,9 +184,6 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
m.body_rootid, m.body_rootid,
m._impl.body_tree, m._impl.body_tree,
m.body_weldid, m.body_weldid,
m._impl.flex_edge,
m._impl.flex_vertadr,
m._impl.flex_vertbodyid,
m.geom_bodyid, m.geom_bodyid,
m.geom_pos, m.geom_pos,
m.geom_quat, m.geom_quat,
@@ -227,25 +191,17 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
m.jnt_pos, m.jnt_pos,
m.jnt_qposadr, m.jnt_qposadr,
m.jnt_type, m.jnt_type,
m._impl.mocap_bodyid,
m._impl.nflexedge,
m._impl.nflexvert,
m.ngeom, m.ngeom,
m.nmocap,
m.nsite, m.nsite,
m.qpos0, m.qpos0,
m.site_bodyid, m.site_bodyid,
m.site_pos, m.site_pos,
m.site_quat, m.site_quat,
d._impl.flexedge_length,
d._impl.flexedge_velocity,
d._impl.flexvert_xpos,
d.geom_xmat, d.geom_xmat,
d.geom_xpos, d.geom_xpos,
d.mocap_pos, d.mocap_pos,
d.mocap_quat, d.mocap_quat,
d.qpos, d.qpos,
d.qvel,
d.site_xmat, d.site_xmat,
d.site_xpos, d.site_xpos,
d.xanchor, d.xanchor,
@@ -257,24 +213,20 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
d.xquat, d.xquat,
) )
d = d.tree_replace({ d = d.tree_replace({
'_impl.flexedge_length': out[0], 'geom_xmat': out[0],
'_impl.flexedge_velocity': out[1], 'geom_xpos': out[1],
'_impl.flexvert_xpos': out[2], 'mocap_pos': out[2],
'geom_xmat': out[3], 'mocap_quat': out[3],
'geom_xpos': out[4], 'qpos': out[4],
'mocap_pos': out[5], 'site_xmat': out[5],
'mocap_quat': out[6], 'site_xpos': out[6],
'qpos': out[7], 'xanchor': out[7],
'qvel': out[8], 'xaxis': out[8],
'site_xmat': out[9], 'ximat': out[9],
'site_xpos': out[10], 'xipos': out[10],
'xanchor': out[11], 'xmat': out[11],
'xaxis': out[12], 'xpos': out[12],
'ximat': out[13], 'xquat': out[13],
'xipos': out[14],
'xmat': out[15],
'xpos': out[16],
'xquat': out[17],
}) })
return d return d
+13 -13
View File
@@ -73,7 +73,7 @@ class SmoothTest(parameterized.TestCase):
_, key1, key2 = jax.random.split(rng, 3) _, key1, key2 = jax.random.split(rng, 3)
mocap_pos = jax.random.normal(key1, (m.nmocap, 3)) mocap_pos = jax.random.normal(key1, (m.nmocap, 3))
mocap_quat = jax.random.normal(key2, (m.nmocap, 4)) mocap_quat = jax.random.normal(key2, (m.nmocap, 4))
mocap_quat = math.normalize(mocap_quat) mocap_quat = math.normalize(mocap_quat, axis=0)
dx = dx.replace(qpos=qpos, mocap_pos=mocap_pos, mocap_quat=mocap_quat) dx = dx.replace(qpos=qpos, mocap_pos=mocap_pos, mocap_quat=mocap_quat)
dx = jax.jit(smooth.kinematics)(mx, dx) dx = jax.jit(smooth.kinematics)(mx, dx)
@@ -81,7 +81,7 @@ class SmoothTest(parameterized.TestCase):
d.qpos[:] = qpos d.qpos[:] = qpos
d.mocap_pos[:] = mocap_pos d.mocap_pos[:] = mocap_pos
d.mocap_quat[:] = mocap_quat d.mocap_quat[:] = mocap_quat
mujoco.mj_forward(m, d) mujoco.mj_kinematics(m, d)
tu.assert_attr_eq(d, dx, 'xanchor') tu.assert_attr_eq(d, dx, 'xanchor')
tu.assert_attr_eq(d, dx, 'xaxis') tu.assert_attr_eq(d, dx, 'xaxis')
@@ -110,9 +110,9 @@ class SmoothTest(parameterized.TestCase):
worldids = jp.arange(batch_size) worldids = jp.arange(batch_size)
dx_batch = jax.vmap(functools.partial(tu.make_data, m))(worldids) dx_batch = jax.vmap(functools.partial(tu.make_data, m))(worldids)
fields = ('xanchor', 'xaxis', 'xpos', 'xquat', 'xmat', 'xipos', 'ximat', # don't zero xmat, ximat, xquat, geom_xpos, or geom_xmat
'geom_xpos', 'geom_xmat', 'site_xpos', 'site_xmat') # fmt: skip # these fields are precomputed in make_data
for f in fields: for f in ('xanchor', 'xaxis', 'xpos', 'xipos', 'site_xpos', 'site_xmat'):
dx_batch = dx_batch.replace(**{f: jp.zeros_like(getattr(dx_batch, f))}) dx_batch = dx_batch.replace(**{f: jp.zeros_like(getattr(dx_batch, f))})
dx_batch = jax.jit(jax.vmap(smooth.kinematics, in_axes=(None, 0)))( dx_batch = jax.jit(jax.vmap(smooth.kinematics, in_axes=(None, 0)))(
@@ -125,7 +125,7 @@ class SmoothTest(parameterized.TestCase):
d.qpos[:] = dx.qpos d.qpos[:] = dx.qpos
d.mocap_pos[:] = dx.mocap_pos d.mocap_pos[:] = dx.mocap_pos
d.mocap_quat[:] = dx.mocap_quat d.mocap_quat[:] = dx.mocap_quat
mujoco.mj_forward(m, d) mujoco.mj_kinematics(m, d)
tu.assert_attr_eq(d, dx, 'xanchor') tu.assert_attr_eq(d, dx, 'xanchor')
tu.assert_attr_eq(d, dx, 'xaxis') tu.assert_attr_eq(d, dx, 'xaxis')
@@ -154,9 +154,9 @@ class SmoothTest(parameterized.TestCase):
worldids = jp.arange(16).reshape((4, 4)) worldids = jp.arange(16).reshape((4, 4))
dx_batch = jax.vmap(jax.vmap(functools.partial(tu.make_data, m)))(worldids) dx_batch = jax.vmap(jax.vmap(functools.partial(tu.make_data, m)))(worldids)
fields = ('xanchor', 'xaxis', 'xpos', 'xquat', 'xmat', 'xipos', 'ximat', # don't zero xmat, ximat, xquat, geom_xpos, or geom_xmat
'geom_xpos', 'geom_xmat', 'site_xpos', 'site_xmat') # fmt: skip # these fields are precomputed in make_data
for f in fields: for f in ('xanchor', 'xaxis', 'xpos', 'xipos', 'site_xpos', 'site_xmat'):
dx_batch = dx_batch.replace(**{f: jp.zeros_like(getattr(dx_batch, f))}) dx_batch = dx_batch.replace(**{f: jp.zeros_like(getattr(dx_batch, f))})
dx_batch = jax.jit( dx_batch = jax.jit(
@@ -208,9 +208,9 @@ class SmoothTest(parameterized.TestCase):
worldids = jp.arange(batch_size) worldids = jp.arange(batch_size)
dx_batch = jax.vmap(functools.partial(tu.make_data, m))(worldids) dx_batch = jax.vmap(functools.partial(tu.make_data, m))(worldids)
fields = ('xanchor', 'xaxis', 'xpos', 'xquat', 'xmat', 'xipos', 'ximat', # don't zero xmat, ximat, xquat, geom_xpos, or geom_xmat
'geom_xpos', 'geom_xmat', 'site_xpos', 'site_xmat') # fmt: skip # these fields are precomputed in make_data
for f in fields: for f in ('xanchor', 'xaxis', 'xpos', 'xipos', 'site_xpos', 'site_xmat'):
dx_batch = dx_batch.replace(**{f: jp.zeros_like(getattr(dx_batch, f))}) dx_batch = dx_batch.replace(**{f: jp.zeros_like(getattr(dx_batch, f))})
dx_batch = jax.jit(jax.vmap(smooth.kinematics, in_axes=(None, 0)))( dx_batch = jax.jit(jax.vmap(smooth.kinematics, in_axes=(None, 0)))(
@@ -224,7 +224,7 @@ class SmoothTest(parameterized.TestCase):
d.mocap_pos[:] = dx.mocap_pos d.mocap_pos[:] = dx.mocap_pos
d.mocap_quat[:] = dx.mocap_quat d.mocap_quat[:] = dx.mocap_quat
m.geom_pos[:] = mx.geom_pos[i] m.geom_pos[:] = mx.geom_pos[i]
mujoco.mj_forward(m, d) mujoco.mj_kinematics(m, d)
tu.assert_attr_eq(d, dx, 'xanchor') tu.assert_attr_eq(d, dx, 'xanchor')
tu.assert_attr_eq(d, dx, 'xaxis') tu.assert_attr_eq(d, dx, 'xaxis')
+35 -13
View File
@@ -62,6 +62,7 @@ class BlockDim:
contact_sort: int contact_sort: int
energy_vel_kinetic: int energy_vel_kinetic: int
euler_dense: int euler_dense: int
linesearch_iterative: int
mul_m_dense: int mul_m_dense: int
ray: int ray: int
segmented_sort: int segmented_sort: int
@@ -69,6 +70,7 @@ class BlockDim:
update_gradient_JTDAJ_dense: int update_gradient_JTDAJ_dense: int
update_gradient_JTDAJ_sparse: int update_gradient_JTDAJ_sparse: int
update_gradient_cholesky: int update_gradient_cholesky: int
update_gradient_cholesky_blocked: int
def tree_flatten(self): def tree_flatten(self):
children = list((getattr(self, k) for k in self.__dataclass_fields__)) children = list((getattr(self, k) for k in self.__dataclass_fields__))
@@ -93,6 +95,7 @@ class OptionWarp(PyTreeNode):
contact_sensor_maxmatch: int contact_sensor_maxmatch: int
graph_conditional: bool graph_conditional: bool
has_fluid: bool has_fluid: bool
impratio_invsqrt: jax.Array
is_sparse: bool is_sparse: bool
ls_parallel: bool ls_parallel: bool
ls_parallel_min_step: float ls_parallel_min_step: float
@@ -113,6 +116,7 @@ class ModelWarp(PyTreeNode):
dof_tri_col: np.ndarray dof_tri_col: np.ndarray
dof_tri_row: np.ndarray dof_tri_row: np.ndarray
eq_connect_adr: np.ndarray eq_connect_adr: np.ndarray
eq_flex_adr: np.ndarray
eq_jnt_adr: np.ndarray eq_jnt_adr: np.ndarray
eq_ten_adr: np.ndarray eq_ten_adr: np.ndarray
eq_wld_adr: np.ndarray eq_wld_adr: np.ndarray
@@ -122,13 +126,17 @@ class ModelWarp(PyTreeNode):
flex_edge: np.ndarray flex_edge: np.ndarray
flex_edgeadr: np.ndarray flex_edgeadr: np.ndarray
flex_edgeflap: np.ndarray flex_edgeflap: np.ndarray
flex_edgenum: np.ndarray
flex_elem: np.ndarray flex_elem: np.ndarray
flex_elemadr: np.ndarray
flex_elemedge: np.ndarray flex_elemedge: np.ndarray
flex_elemedgeadr: np.ndarray flex_elemedgeadr: np.ndarray
flex_elemnum: np.ndarray
flex_stiffness: np.ndarray flex_stiffness: np.ndarray
flex_vertadr: np.ndarray flex_vertadr: np.ndarray
flex_vertbodyid: np.ndarray flex_vertbodyid: np.ndarray
flex_vertnum: np.ndarray flex_vertnum: np.ndarray
flexedge_invweight0: np.ndarray
flexedge_length0: np.ndarray flexedge_length0: np.ndarray
geom_pair_type_count: Tuple[int, ...] geom_pair_type_count: Tuple[int, ...]
geom_plugin_index: np.ndarray geom_plugin_index: np.ndarray
@@ -167,6 +175,7 @@ class ModelWarp(PyTreeNode):
nsensorcollision: int nsensorcollision: int
nsensorcontact: int nsensorcontact: int
nsensortaxel: int nsensortaxel: int
nv_pad: int
nxn_geom_pair: np.ndarray nxn_geom_pair: np.ndarray
nxn_geom_pair_filtered: np.ndarray nxn_geom_pair_filtered: np.ndarray
nxn_pairid: np.ndarray nxn_pairid: np.ndarray
@@ -249,8 +258,6 @@ class DataWarp(PyTreeNode):
efc__alpha: jax.Array efc__alpha: jax.Array
efc__aref: jax.Array efc__aref: jax.Array
efc__beta: jax.Array efc__beta: jax.Array
efc__cholesky_L_tmp: jax.Array
efc__cholesky_y_tmp: jax.Array
efc__cost: jax.Array efc__cost: jax.Array
efc__done: jax.Array efc__done: jax.Array
efc__force: jax.Array efc__force: jax.Array
@@ -258,7 +265,6 @@ class DataWarp(PyTreeNode):
efc__gauss: jax.Array efc__gauss: jax.Array
efc__grad: jax.Array efc__grad: jax.Array
efc__grad_dot: jax.Array efc__grad_dot: jax.Array
efc__h: jax.Array
efc__id: jax.Array efc__id: jax.Array
efc__jv: jax.Array efc__jv: jax.Array
efc__margin: jax.Array efc__margin: jax.Array
@@ -275,6 +281,7 @@ class DataWarp(PyTreeNode):
efc__type: jax.Array efc__type: jax.Array
efc__vel: jax.Array efc__vel: jax.Array
energy: jax.Array energy: jax.Array
flexedge_J: jax.Array
flexedge_length: jax.Array flexedge_length: jax.Array
flexedge_velocity: jax.Array flexedge_velocity: jax.Array
flexvert_xpos: jax.Array flexvert_xpos: jax.Array
@@ -285,6 +292,7 @@ class DataWarp(PyTreeNode):
ncollision: jax.Array ncollision: jax.Array
ne: jax.Array ne: jax.Array
ne_connect: jax.Array ne_connect: jax.Array
ne_flex: jax.Array
ne_jnt: jax.Array ne_jnt: jax.Array
ne_ten: jax.Array ne_ten: jax.Array
ne_weld: jax.Array ne_weld: jax.Array
@@ -404,8 +412,6 @@ _NDIM = {
'efc__alpha': 1, 'efc__alpha': 1,
'efc__aref': 2, 'efc__aref': 2,
'efc__beta': 1, 'efc__beta': 1,
'efc__cholesky_L_tmp': 3,
'efc__cholesky_y_tmp': 2,
'efc__cost': 1, 'efc__cost': 1,
'efc__done': 1, 'efc__done': 1,
'efc__force': 2, 'efc__force': 2,
@@ -413,7 +419,6 @@ _NDIM = {
'efc__gauss': 1, 'efc__gauss': 1,
'efc__grad': 2, 'efc__grad': 2,
'efc__grad_dot': 1, 'efc__grad_dot': 1,
'efc__h': 3,
'efc__id': 2, 'efc__id': 2,
'efc__jv': 2, 'efc__jv': 2,
'efc__margin': 2, 'efc__margin': 2,
@@ -431,6 +436,7 @@ _NDIM = {
'efc__vel': 2, 'efc__vel': 2,
'energy': 2, 'energy': 2,
'eq_active': 2, 'eq_active': 2,
'flexedge_J': 3,
'flexedge_length': 2, 'flexedge_length': 2,
'flexedge_velocity': 2, 'flexedge_velocity': 2,
'flexvert_xpos': 3, 'flexvert_xpos': 3,
@@ -445,6 +451,7 @@ _NDIM = {
'ncollision': 1, 'ncollision': 1,
'ne': 1, 'ne': 1,
'ne_connect': 1, 'ne_connect': 1,
'ne_flex': 1,
'ne_jnt': 1, 'ne_jnt': 1,
'ne_ten': 1, 'ne_ten': 1,
'ne_weld': 1, 'ne_weld': 1,
@@ -531,6 +538,7 @@ _NDIM = {
'block_dim__contact_sort': 0, 'block_dim__contact_sort': 0,
'block_dim__energy_vel_kinetic': 0, 'block_dim__energy_vel_kinetic': 0,
'block_dim__euler_dense': 0, 'block_dim__euler_dense': 0,
'block_dim__linesearch_iterative': 0,
'block_dim__mul_m_dense': 0, 'block_dim__mul_m_dense': 0,
'block_dim__ray': 0, 'block_dim__ray': 0,
'block_dim__segmented_sort': 0, 'block_dim__segmented_sort': 0,
@@ -538,6 +546,7 @@ _NDIM = {
'block_dim__update_gradient_JTDAJ_dense': 0, 'block_dim__update_gradient_JTDAJ_dense': 0,
'block_dim__update_gradient_JTDAJ_sparse': 0, 'block_dim__update_gradient_JTDAJ_sparse': 0,
'block_dim__update_gradient_cholesky': 0, 'block_dim__update_gradient_cholesky': 0,
'block_dim__update_gradient_cholesky_blocked': 0,
'body_conaffinity': 1, 'body_conaffinity': 1,
'body_contype': 1, 'body_contype': 1,
'body_dofadr': 1, 'body_dofadr': 1,
@@ -589,6 +598,7 @@ _NDIM = {
'eq_active0': 1, 'eq_active0': 1,
'eq_connect_adr': 1, 'eq_connect_adr': 1,
'eq_data': 3, 'eq_data': 3,
'eq_flex_adr': 1,
'eq_jnt_adr': 1, 'eq_jnt_adr': 1,
'eq_obj1id': 1, 'eq_obj1id': 1,
'eq_obj2id': 1, 'eq_obj2id': 1,
@@ -605,13 +615,17 @@ _NDIM = {
'flex_edge': 2, 'flex_edge': 2,
'flex_edgeadr': 1, 'flex_edgeadr': 1,
'flex_edgeflap': 2, 'flex_edgeflap': 2,
'flex_edgenum': 1,
'flex_elem': 1, 'flex_elem': 1,
'flex_elemadr': 1,
'flex_elemedge': 1, 'flex_elemedge': 1,
'flex_elemedgeadr': 1, 'flex_elemedgeadr': 1,
'flex_elemnum': 1,
'flex_stiffness': 2, 'flex_stiffness': 2,
'flex_vertadr': 1, 'flex_vertadr': 1,
'flex_vertbodyid': 1, 'flex_vertbodyid': 1,
'flex_vertnum': 1, 'flex_vertnum': 1,
'flexedge_invweight0': 1,
'flexedge_length0': 1, 'flexedge_length0': 1,
'geom_aabb': 4, 'geom_aabb': 4,
'geom_bodyid': 1, 'geom_bodyid': 1,
@@ -743,6 +757,7 @@ _NDIM = {
'ntendon': 0, 'ntendon': 0,
'nu': 0, 'nu': 0,
'nv': 0, 'nv': 0,
'nv_pad': 0,
'nwrap': 0, 'nwrap': 0,
'nxn_geom_pair': 2, 'nxn_geom_pair': 2,
'nxn_geom_pair_filtered': 2, 'nxn_geom_pair_filtered': 2,
@@ -761,7 +776,7 @@ _NDIM = {
'opt__graph_conditional': 0, 'opt__graph_conditional': 0,
'opt__gravity': 2, 'opt__gravity': 2,
'opt__has_fluid': 0, 'opt__has_fluid': 0,
'opt__impratio': 1, 'opt__impratio_invsqrt': 1,
'opt__integrator': 0, 'opt__integrator': 0,
'opt__is_sparse': 0, 'opt__is_sparse': 0,
'opt__iterations': 0, 'opt__iterations': 0,
@@ -878,7 +893,7 @@ _NDIM = {
'graph_conditional': 0, 'graph_conditional': 0,
'gravity': 2, 'gravity': 2,
'has_fluid': 0, 'has_fluid': 0,
'impratio': 1, 'impratio_invsqrt': 1,
'integrator': 0, 'integrator': 0,
'is_sparse': 0, 'is_sparse': 0,
'iterations': 0, 'iterations': 0,
@@ -942,8 +957,6 @@ _BATCH_DIM = {
'efc__alpha': True, 'efc__alpha': True,
'efc__aref': True, 'efc__aref': True,
'efc__beta': True, 'efc__beta': True,
'efc__cholesky_L_tmp': True,
'efc__cholesky_y_tmp': True,
'efc__cost': True, 'efc__cost': True,
'efc__done': True, 'efc__done': True,
'efc__force': True, 'efc__force': True,
@@ -951,7 +964,6 @@ _BATCH_DIM = {
'efc__gauss': True, 'efc__gauss': True,
'efc__grad': True, 'efc__grad': True,
'efc__grad_dot': True, 'efc__grad_dot': True,
'efc__h': True,
'efc__id': True, 'efc__id': True,
'efc__jv': True, 'efc__jv': True,
'efc__margin': True, 'efc__margin': True,
@@ -969,6 +981,7 @@ _BATCH_DIM = {
'efc__vel': True, 'efc__vel': True,
'energy': True, 'energy': True,
'eq_active': True, 'eq_active': True,
'flexedge_J': True,
'flexedge_length': True, 'flexedge_length': True,
'flexedge_velocity': True, 'flexedge_velocity': True,
'flexvert_xpos': True, 'flexvert_xpos': True,
@@ -983,6 +996,7 @@ _BATCH_DIM = {
'ncollision': False, 'ncollision': False,
'ne': True, 'ne': True,
'ne_connect': True, 'ne_connect': True,
'ne_flex': True,
'ne_jnt': True, 'ne_jnt': True,
'ne_ten': True, 'ne_ten': True,
'ne_weld': True, 'ne_weld': True,
@@ -1069,6 +1083,7 @@ _BATCH_DIM = {
'block_dim__contact_sort': False, 'block_dim__contact_sort': False,
'block_dim__energy_vel_kinetic': False, 'block_dim__energy_vel_kinetic': False,
'block_dim__euler_dense': False, 'block_dim__euler_dense': False,
'block_dim__linesearch_iterative': False,
'block_dim__mul_m_dense': False, 'block_dim__mul_m_dense': False,
'block_dim__ray': False, 'block_dim__ray': False,
'block_dim__segmented_sort': False, 'block_dim__segmented_sort': False,
@@ -1076,6 +1091,7 @@ _BATCH_DIM = {
'block_dim__update_gradient_JTDAJ_dense': False, 'block_dim__update_gradient_JTDAJ_dense': False,
'block_dim__update_gradient_JTDAJ_sparse': False, 'block_dim__update_gradient_JTDAJ_sparse': False,
'block_dim__update_gradient_cholesky': False, 'block_dim__update_gradient_cholesky': False,
'block_dim__update_gradient_cholesky_blocked': False,
'body_conaffinity': False, 'body_conaffinity': False,
'body_contype': False, 'body_contype': False,
'body_dofadr': False, 'body_dofadr': False,
@@ -1127,6 +1143,7 @@ _BATCH_DIM = {
'eq_active0': False, 'eq_active0': False,
'eq_connect_adr': False, 'eq_connect_adr': False,
'eq_data': True, 'eq_data': True,
'eq_flex_adr': False,
'eq_jnt_adr': False, 'eq_jnt_adr': False,
'eq_obj1id': False, 'eq_obj1id': False,
'eq_obj2id': False, 'eq_obj2id': False,
@@ -1143,13 +1160,17 @@ _BATCH_DIM = {
'flex_edge': False, 'flex_edge': False,
'flex_edgeadr': False, 'flex_edgeadr': False,
'flex_edgeflap': False, 'flex_edgeflap': False,
'flex_edgenum': False,
'flex_elem': False, 'flex_elem': False,
'flex_elemadr': False,
'flex_elemedge': False, 'flex_elemedge': False,
'flex_elemedgeadr': False, 'flex_elemedgeadr': False,
'flex_elemnum': False,
'flex_stiffness': False, 'flex_stiffness': False,
'flex_vertadr': False, 'flex_vertadr': False,
'flex_vertbodyid': False, 'flex_vertbodyid': False,
'flex_vertnum': False, 'flex_vertnum': False,
'flexedge_invweight0': False,
'flexedge_length0': False, 'flexedge_length0': False,
'geom_aabb': True, 'geom_aabb': True,
'geom_bodyid': False, 'geom_bodyid': False,
@@ -1281,6 +1302,7 @@ _BATCH_DIM = {
'ntendon': False, 'ntendon': False,
'nu': False, 'nu': False,
'nv': False, 'nv': False,
'nv_pad': False,
'nwrap': False, 'nwrap': False,
'nxn_geom_pair': False, 'nxn_geom_pair': False,
'nxn_geom_pair_filtered': False, 'nxn_geom_pair_filtered': False,
@@ -1299,7 +1321,7 @@ _BATCH_DIM = {
'opt__graph_conditional': False, 'opt__graph_conditional': False,
'opt__gravity': True, 'opt__gravity': True,
'opt__has_fluid': False, 'opt__has_fluid': False,
'opt__impratio': True, 'opt__impratio_invsqrt': True,
'opt__integrator': False, 'opt__integrator': False,
'opt__is_sparse': False, 'opt__is_sparse': False,
'opt__iterations': False, 'opt__iterations': False,
@@ -1416,7 +1438,7 @@ _BATCH_DIM = {
'graph_conditional': False, 'graph_conditional': False,
'gravity': True, 'gravity': True,
'has_fluid': False, 'has_fluid': False,
'impratio': True, 'impratio_invsqrt': True,
'integrator': False, 'integrator': False,
'is_sparse': False, 'is_sparse': False,
'iterations': False, 'iterations': False,