Skip to content

Commit 10f93f8

Browse files
duburcqachris-la-humalab
authored andcommitted
[BUG FIX] Fix MPR warm-start infinite loop edge case. (Genesis-Embodied-AI#1336)
* Disable timers by default. * Fix compiler warnings for excessive inlining. * Fix running benchmarks sequentially. * Fix benchmark master worker id shadowing. * Fix contribution guidelines. * Re-enable warning for exceeding max collision pairs. * Offset geometry centers for MPR only if necessary. * Fix MPR warm-start infinite loop edge case.
1 parent ab0213d commit 10f93f8

File tree

12 files changed

+67
-65
lines changed

12 files changed

+67
-65
lines changed

.github/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Thank you for your interest in contributing to Genesis! We welcome contributions
6161
- (Optional) You can run CI tests locally to ensure you pass the online CI checks.
6262

6363
```python
64-
python -m unittest discover tests
64+
pytest -v --forked -m required ./tests
6565
```
6666

6767
- In the title of your Pull Request, please include [BUG FIX], [FEATURE] or [MISC] to indicate the purpose.

genesis/engine/materials/FEM/elastic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ def compute_energy_gradient_hessian_linear_corotated(self, mu, lam, J, F, actu,
432432
for i, k in ti.static(ti.ndrange(3, 3)):
433433
hessian_field[i_b, i, i, i_e][k, k] = mu
434434

435-
for i, j, alpha, beta in ti.static(ti.ndrange(3, 3, 3, 3)):
435+
for i, j, alpha, beta in ti.ndrange(3, 3, 3, 3):
436436
hessian_field[i_b, j, beta, i_e][i, alpha] += mu * R[i, beta] * R[alpha, j] + lam * R[alpha, beta] * R[i, j]
437437

438438
return energy, gradient

genesis/engine/simulator.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ def _add_force_field(self, force_field):
183183
solver._add_force_field(force_field)
184184

185185
def build(self):
186-
187186
self.n_envs = self.scene.n_envs
188187
self._B = self.scene._B
189188
self._para_level = self.scene._para_level

genesis/engine/solvers/fem_solver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ def accumulate_vertex_force_preconditioner(self, f: ti.i32):
617617
)
618618

619619
# diagonal 3-by-3 block of hessian
620-
for k, i, j in ti.static(ti.ndrange(4, 3, 3)):
620+
for k, i, j in ti.ndrange(4, 3, 3):
621621
self.pcg_state_v[i_b, i_v[k]].diag3x3 += (
622622
V * damping_beta_factor * S[k, i] * S[k, j] * self.elements_el_hessian[i_b, i, j, i_e]
623623
)

genesis/engine/solvers/rigid/collider_decomp.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ def _init_collision_fields(self) -> None:
153153
self._max_collision_pairs = min(n_possible_pairs, self._solver._max_collision_pairs)
154154
self._max_contact_pairs = self._max_collision_pairs * self._n_contacts_per_pair
155155

156+
self._warn_msg_max_collision_pairs = (
157+
f"{colors.YELLOW}[Genesis] [00:00:00] [WARNING] Ignoring contact pair to avoid exceeding max "
158+
f"({self._max_contact_pairs}). Please increase the value of RigidSolver's option "
159+
f"'max_collision_pairs'.{formats.RESET}"
160+
)
161+
156162
############## broad phase SAP ##############
157163
# This buffer stores the AABBs along the search axis of all geoms
158164
struct_sort_buffer = ti.types.struct(value=gs.ti_float, i_g=gs.ti_int, is_max=gs.ti_int)
@@ -299,23 +305,23 @@ def _kernel_clear(
299305
self.n_contacts[i_b] = 0
300306

301307
def detection(self) -> None:
302-
from genesis.utils.tools import create_timer
308+
# from genesis.utils.tools import create_timer
303309

304310
self._contacts_info_cache = {}
305-
timer = create_timer(name="69477ab0-5e75-47cb-a4a5-d4eebd9336ca", level=3, ti_sync=True, skip_first_call=True)
311+
# timer = create_timer(name="69477ab0-5e75-47cb-a4a5-d4eebd9336ca", level=3, ti_sync=True, skip_first_call=True)
306312
self._func_update_aabbs()
307-
timer.stamp("func_update_aabbs")
313+
# timer.stamp("func_update_aabbs")
308314
self._func_broad_phase()
309-
timer.stamp("func_broad_phase")
315+
# timer.stamp("func_broad_phase")
310316
self._func_narrow_phase_convex_vs_convex()
311317
self._func_narrow_phase_convex_specializations()
312-
timer.stamp("func_narrow_phase")
318+
# timer.stamp("func_narrow_phase")
313319
if self._has_terrain:
314320
self._func_narrow_phase_any_vs_terrain()
315-
timer.stamp("_func_narrow_phase_any_vs_terrain")
321+
# timer.stamp("_func_narrow_phase_any_vs_terrain")
316322
if self._has_nonconvex_nonterrain:
317323
self._func_narrow_phase_nonconvex_vs_nonterrain()
318-
timer.stamp("_func_narrow_phase_nonconvex_vs_nonterrain")
324+
# timer.stamp("_func_narrow_phase_nonconvex_vs_nonterrain")
319325

320326
@ti.func
321327
def _func_point_in_geom_aabb(self, point, i_g, i_b):
@@ -770,11 +776,7 @@ def _func_broad_phase(self):
770776
continue
771777

772778
if self.n_broad_pairs[i_b] == self._max_collision_pairs:
773-
# print(
774-
# f"{colors.YELLOW}[Genesis] [00:00:00] [WARNING] Ignoring collision pair to avoid "
775-
# f"exceeding max ({self._max_collision_pairs}). Please increase the value of "
776-
# f"RigidSolver's option 'max_collision_pairs'.{formats.RESET}"
777-
# )
779+
ti.static_print(self._warn_msg_max_collision_pairs)
778780
break
779781
self.broad_collision_pairs[self.n_broad_pairs[i_b], i_b][0] = i_ga
780782
self.broad_collision_pairs[self.n_broad_pairs[i_b], i_b][1] = i_gb
@@ -1107,12 +1109,7 @@ def _func_add_contact(self, i_ga, i_gb, normal, contact_pos, penetration, i_b):
11071109
i_col = self.n_contacts[i_b]
11081110

11091111
if i_col == self._max_contact_pairs:
1110-
# print(
1111-
# f"{colors.YELLOW}[Genesis] [00:00:00] [WARNING] Ignoring contact pair to avoid exceeding max "
1112-
# f"({self._max_contact_pairs}). Please increase the value of RigidSolver's option "
1113-
# f"'max_collision_pairs'.{formats.RESET}"
1114-
# )
1115-
pass
1112+
ti.static_print(self._warn_msg_max_collision_pairs)
11161113
else:
11171114
ga_info = self._solver.geoms_info[i_ga]
11181115
gb_info = self._solver.geoms_info[i_gb]
@@ -1299,6 +1296,7 @@ def _func_convex_convex_contact(self, i_ga, i_gb, i_b):
12991296
# Because of this, it is necessary to run it twice and take the contact information
13001297
# associated with the point of deepest penetration.
13011298
try_sdf = True
1299+
13021300
### GJK
13031301
elif ti.static(self.ccd_algorithm == CCD_ALGORITHM_CODE.GJK):
13041302
# If it was not the first detection, only detect single contact point.
@@ -1313,12 +1311,11 @@ def _func_convex_convex_contact(self, i_ga, i_gb, i_b):
13131311
# Used MuJoCo's multi-contact algorithm to find multiple contact points. Therefore,
13141312
# add the discovered contact points and stop multi-contact search.
13151313
for i_c in range(n_contacts):
1316-
if i_c >= self._n_contacts_per_pair:
1317-
# Ignore contact points if the number of contacts exceeds the limit.
1318-
break
1319-
contact_pos = self._gjk.contact_pos[i_b, i_c]
1320-
normal = self._gjk.normal[i_b, i_c]
1321-
self._func_add_contact(i_ga, i_gb, normal, contact_pos, penetration, i_b)
1314+
# Ignore contact points if the number of contacts exceeds the limit.
1315+
if i_c < self._n_contacts_per_pair:
1316+
contact_pos = self._gjk.contact_pos[i_b, i_c]
1317+
normal = self._gjk.normal[i_b, i_c]
1318+
self._func_add_contact(i_ga, i_gb, normal, contact_pos, penetration, i_b)
13221319

13231320
break
13241321
else:

genesis/engine/solvers/rigid/constraint_solver_decomp.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -750,17 +750,17 @@ def handle_constraints(self):
750750
self.resolve()
751751

752752
def resolve(self):
753-
from genesis.utils.tools import create_timer
753+
# from genesis.utils.tools import create_timer
754754

755-
timer = create_timer(name="resolve", level=3, ti_sync=True, skip_first_call=True)
755+
# timer = create_timer(name="resolve", level=3, ti_sync=True, skip_first_call=True)
756756
self._func_init_solver()
757-
timer.stamp("_func_init_solver")
757+
# timer.stamp("_func_init_solver")
758758
self._func_solve()
759-
timer.stamp("_func_solve")
759+
# timer.stamp("_func_solve")
760760
self._func_update_qacc()
761-
timer.stamp("_func_update_qacc")
761+
# timer.stamp("_func_update_qacc")
762762
self._func_update_contact_force()
763-
timer.stamp("compute force")
763+
# timer.stamp("compute force")
764764

765765
@ti.kernel
766766
def _func_update_contact_force(self):

genesis/engine/solvers/rigid/mpr_decomp.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,9 +402,11 @@ def mpr_discover_portal(self, i_ga, i_gb, i_b, normal_ws):
402402
center_b = gu.ti_transform_by_trans_quat(center_b_local, g_state_b.pos, g_state_b.quat)
403403
delta = center_a - center_b
404404

405-
# Skip offset if normal is almost colinear already
405+
# Skip offset if normal is roughly pointing in the same direction already.
406+
# Note that a threshold of 0.5 would probably make more sense, but this means that the center of each
407+
# geometry would significantly affect collision detection, which is undesirable.
406408
normal = delta.normalized()
407-
if normal_ws.cross(normal).norm() > self.CCD_TOLERANCE:
409+
if normal_ws.cross(normal).norm() > 0.01:
408410
# Compute the target offset
409411
offset = delta.dot(normal_ws) * normal_ws - delta
410412
offset_norm = offset.norm()
@@ -476,6 +478,10 @@ def mpr_discover_portal(self, i_ga, i_gb, i_b, normal_ws):
476478
self.mpr_swap(1, 2, i_ga, i_gb, i_b)
477479
direction = -direction
478480

481+
# FIXME: This algorithm may get stuck in an infinite loop if the actually penetration is smaller
482+
# then `CCD_EPS` and at least one of the center of each geometry is outside their convex hull.
483+
# Since this deadlock happens very rarely, a simple fix is to abord computation after a few trials.
484+
num_trials = gs.ti_int(0)
479485
while self.simplex_size[i_b] < 4:
480486
v, v1, v2 = self.compute_support(direction, i_ga, i_gb, i_b)
481487
dot = v.dot(direction)
@@ -507,6 +513,10 @@ def mpr_discover_portal(self, i_ga, i_gb, i_b, normal_ws):
507513
vb = self.simplex_support[2, i_b].v - self.simplex_support[0, i_b].v
508514
direction = va.cross(vb)
509515
direction = direction.normalized()
516+
num_trials = num_trials + 1
517+
if num_trials == 15:
518+
ret = -1
519+
break
510520
else:
511521
self.simplex_support[3, i_b].v1 = v1
512522
self.simplex_support[3, i_b].v2 = v2

genesis/engine/solvers/rigid/rigid_solver_decomp.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,18 +1705,15 @@ def _kernel_clear_external_force(self):
17051705
self._func_clear_external_force()
17061706

17071707
def substep(self):
1708-
from genesis.utils.tools import create_timer
1708+
# from genesis.utils.tools import create_timer
17091709

1710-
timer = create_timer("rigid", level=1, ti_sync=True, skip_first_call=True)
1710+
# timer = create_timer("rigid", level=1, ti_sync=True, skip_first_call=True)
17111711
self._kernel_step_1()
1712-
timer.stamp("kernel_step_1")
1713-
1714-
# constraint force
1712+
# timer.stamp("kernel_step_1")
17151713
self._func_constraint_force()
1716-
timer.stamp("constraint_force")
1717-
1714+
# timer.stamp("constraint_force")
17181715
self._kernel_step_2()
1719-
timer.stamp("kernel_step_2")
1716+
# timer.stamp("kernel_step_2")
17201717

17211718
@ti.kernel
17221719
def _kernel_step_1(self):
@@ -1800,20 +1797,20 @@ def _kernel_forward_kinematics_links_geoms(self, envs_idx: ti.types.ndarray()):
18001797
self._func_update_geoms(i_b)
18011798

18021799
def _func_constraint_force(self):
1803-
from genesis.utils.tools import create_timer
1800+
# from genesis.utils.tools import create_timer
18041801

1805-
timer = create_timer(name="constraint_force", level=2, ti_sync=True, skip_first_call=True)
1802+
# timer = create_timer(name="constraint_force", level=2, ti_sync=True, skip_first_call=True)
18061803
if self._enable_collision or self._enable_joint_limit or self.n_equalities > 0:
18071804
self._func_constraint_clear()
1808-
timer.stamp("constraint_solver.clear")
1805+
# timer.stamp("constraint_solver.clear")
18091806

18101807
if self._enable_collision:
18111808
self.collider.detection()
1812-
timer.stamp("detection")
1809+
# timer.stamp("detection")
18131810

18141811
if not self._disable_constraint:
18151812
self.constraint_solver.handle_constraints()
1816-
timer.stamp("constraint_solver.handle_constraints")
1813+
# timer.stamp("constraint_solver.handle_constraints")
18171814

18181815
@ti.kernel
18191816
def _func_constraint_clear(self):

genesis/utils/bvh.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -693,25 +693,19 @@ def cd_aggregate(self):
693693
self.contact_aggregate[ga, gb].penetration = penetration
694694

695695
def collision_detection(self):
696-
from genesis.utils.tools import create_timer
696+
# from genesis.utils.tools import create_timer
697697

698-
timer = create_timer(name="solve_quadratic", level=4, ti_sync=True, skip_first_call=True)
698+
# timer = create_timer(name="solve_quadratic", level=4, ti_sync=True, skip_first_call=True)
699699
self.cd_init()
700-
timer.stamp("cd_init")
700+
# timer.stamp("cd_init")
701701
self.refit_func()
702-
timer.stamp("refit_func")
702+
# timer.stamp("refit_func")
703703
self.cd_tree_phase() # 18 ms
704-
timer.stamp("cd_tree_phase")
704+
# timer.stamp("cd_tree_phase")
705705
self.cd_aggregate()
706-
timer.stamp("cd_aggregate")
706+
# timer.stamp("cd_aggregate")
707707
# print("overlapped_face, overlapped_node", self.cd_counter[0], self.cd_counter[1])
708708

709-
# exit()
710-
# self.cd_candidate_phase() # not too much
711-
# print("self.cd_counter 2", self.cd_counter)
712-
# self.cd_impact_phase() # 1 ms
713-
# print("self.cd_counter 3", self.cd_counter)
714-
715709
@ti.kernel
716710
def compute_constraint_system(self, n_con: int, n_dof: int):
717711
# TODO TEST

genesis/utils/tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def reset(self):
6565
self.just_reset = True
6666
if self.level == 0 and not self.skip:
6767
print("─" * os.get_terminal_size()[0])
68-
if self.ti_sync:
68+
if self.ti_sync and not self.skip:
6969
ti.sync()
7070
self.prev_time = self.init_time = time.perf_counter()
7171

0 commit comments

Comments
 (0)