1+ #!/usr/bin/env python3
2+ """
3+ This script benchmarks collision checking for two MuJoCo models by randomizing joint positions within the URDF-defined joint limits.
4+ It also adds static obstacles from a URDF file and checks for collisions both within the robot (self-collisions) and between the robot and obstacles.
5+ Additionally, an interactive visualization mode is provided that uses MuJoCo's viewer.
6+ """
7+
8+ import mujoco
9+ import mujoco .viewer
10+ import numpy as np
11+ import time
12+ import sys
13+ from scipy .spatial .transform import Rotation
14+
15+
16+ spec_a = mujoco .MjSpec .from_file ("../../assets/panda/panda.urdf" )
17+ spec_b = mujoco .MjSpec .from_file ("../../assets/panda/spherized_panda.urdf" )
18+
19+ # Add a ground plane to each model.
20+ spec_a .worldbody .add_geom (
21+ type = mujoco .mjtGeom .mjGEOM_PLANE ,
22+ size = [2 , 2 , 0.1 ],
23+ rgba = [0.2 , 0.2 , 0.2 , 1 ]
24+ )
25+
26+
27+ spec_b .worldbody .add_geom (
28+ type = mujoco .mjtGeom .mjGEOM_PLANE ,
29+ size = [2 , 2 , 0.1 ],
30+ rgba = [0.2 , 0.2 , 0.2 , 1 ]
31+ )
32+
33+ # Load obstacles from obstacles.urdf
34+ def load_obstacles_from_urdf (spec , urdf_path ):
35+ """
36+ Loads obstacles from a given URDF file and adds them to the simulation.
37+ Returns a list of added geometry objects.
38+ """
39+ obstacle_geoms = []
40+
41+ # Load the URDF file
42+ obstacle_spec = mujoco .MjSpec .from_file (urdf_path )
43+
44+ for geom in obstacle_spec .worldbody .geoms : # Changed from geom to geoms
45+ obstacle_geom = spec .worldbody .add_geom (
46+ type = geom .type ,
47+ size = geom .size ,
48+ pos = geom .pos if hasattr (geom , 'pos' ) else None ,
49+ quat = geom .quat if hasattr (geom , 'quat' ) else None ,
50+ rgba = geom .rgba if hasattr (geom , 'rgba' ) else [1 , 1 , 1 , 1 ],
51+ condim = geom .condim if hasattr (geom , 'condim' ) else 3 ,
52+ friction = geom .friction if hasattr (geom , 'friction' ) else [1 , 0.1 , 0.1 ]
53+ )
54+ obstacle_geoms .append (obstacle_geom )
55+
56+ # Handle any child bodies recursively
57+ if hasattr (obstacle_spec .worldbody , 'body' ):
58+ for body in obstacle_spec .worldbody .body :
59+ # Add a new body to the spec
60+ new_body = spec .worldbody .add_body ()
61+
62+ # Copy over the body properties
63+ if hasattr (body , 'pos' ):
64+ new_body .pos = body .pos
65+ if hasattr (body , 'quat' ):
66+ new_body .quat = body .quat
67+
68+ # Add all geoms from this body
69+ for geom in body .geoms : # Changed from geom to geoms
70+ obstacle_geom = new_body .add_geom (
71+ type = geom .type ,
72+ size = geom .size ,
73+ pos = geom .pos if hasattr (geom , 'pos' ) else None ,
74+ quat = geom .quat if hasattr (geom , 'quat' ) else None ,
75+ rgba = geom .rgba if hasattr (geom , 'rgba' ) else [1 , 1 , 1 , 1 ],
76+ condim = geom .condim if hasattr (geom , 'condim' ) else 3 ,
77+ friction = geom .friction if hasattr (geom , 'friction' ) else [1 , 0.1 , 0.1 ]
78+ )
79+ obstacle_geoms .append (obstacle_geom )
80+ return obstacle_geoms
81+
82+ obstacles_urdf_path = "../obstacles.urdf"
83+ obstacles_geoms = load_obstacles_from_urdf (spec_a , obstacles_urdf_path )
84+
85+ # Function to randomize joint positions within the limits of the URDF
86+ def random_joint_positions_from_limits (model ):
87+ """
88+ Generates random joint positions that respect the joint limits provided
89+ in the URDF for a fixed-base robot. Assumes that model.nq (number of generalized
90+ coordinates) equals model.njnt (number of joints with limits).
91+ """
92+ qpos = np .empty (model .nq )
93+ for i in range (model .njnt ):
94+ lower , upper = model .jnt_range [i ]
95+ if lower == upper :
96+ qpos [i ] = lower
97+ else :
98+ qpos [i ] = np .random .uniform (lower , upper )
99+ return qpos
100+
101+ def debug_simulation (model , data , step , threshold = 1e3 ):
102+ unstable = False
103+ for i , acc in enumerate (data .qacc ):
104+ if np .isnan (acc ) or np .isinf (acc ) or np .abs (acc ) > threshold :
105+ print (f"Warning: Unstable acceleration at DOF { i } on step { step } : { acc } " )
106+ unstable = True
107+ if unstable :
108+ print ("---- Debug Information ----" )
109+ print (f"Time: { data .time :.4f} " )
110+ print ("qpos:" , data .qpos )
111+ print ("qvel:" , data .qvel )
112+ print ("qacc:" , data .qacc )
113+ print ("---------------------------" )
114+
115+ def benchmark_collision_checking (spec , obstacle_geom_ids , num_samples = 100_000 ):
116+ model = spec .compile ()
117+ data = mujoco .MjData (model )
118+
119+ print (model .ngeom )
120+ print ("Model nq =" , model .nq , "model njnt =" , model .njnt )
121+
122+ obstacle_geom_indices = set ()
123+ for geom_obj in obstacle_geom_ids :
124+ obstacle_geom_indices .add (geom_obj .id )
125+
126+ self_collision_count = 0
127+ obstacle_collision_count = 0
128+ collision_check_total_time = 0.0
129+
130+ start_time = time .perf_counter ()
131+
132+ for _ in range (num_samples ):
133+ data .qpos [:] = random_joint_positions_from_limits (model )
134+ data .qvel [:] = 0
135+ data .ctrl [:] = 0
136+
137+ start_check = time .perf_counter ()
138+ mujoco .mj_forward (model , data )
139+
140+ for i in range (data .ncon ):
141+ contact = data .contact [i ]
142+ if contact .dist < 0 : # Collision detected
143+ geom1 , geom2 = contact .geom1 , contact .geom2
144+
145+ if geom1 in obstacle_geom_indices or geom2 in obstacle_geom_indices :
146+ obstacle_collision_count += 1
147+ else :
148+ self_collision_count += 1
149+
150+ collision_check_total_time += time .perf_counter () - start_check
151+
152+ elapsed_time = time .perf_counter () - start_time
153+
154+ avg_self_collisions = self_collision_count / num_samples
155+ avg_obstacle_collisions = obstacle_collision_count / num_samples
156+ avg_collision_check_time = collision_check_total_time / num_samples
157+ avg_elapsed_time = elapsed_time / num_samples
158+
159+ return avg_self_collisions , avg_obstacle_collisions , avg_elapsed_time , avg_collision_check_time
160+
161+
162+ def benchmark_collision_checking_visual (spec , obstacle_geom_ids , sleep_time = 0.0001 , num_samples = 100_000 ):
163+ model = spec .compile ()
164+ data = mujoco .MjData (model )
165+ obstacle_geom_indices = set ()
166+ for geom_obj in obstacle_geom_ids :
167+ obstacle_geom_indices .add (geom_obj .id )
168+
169+ self_collision_count = 0
170+ obstacle_collision_count = 0
171+ collision_check_total_time = 0.0
172+
173+ start_time = time .perf_counter ()
174+
175+ with mujoco .viewer .launch_passive (model , data ) as viewer :
176+ for _ in range (num_samples ):
177+ data .qpos [:] = random_joint_positions_from_limits (model )
178+ data .qvel [:] = 0
179+ data .ctrl [:] = 0
180+
181+ start_check = time .perf_counter ()
182+ mujoco .mj_forward (model , data )
183+
184+ for i in range (data .ncon ):
185+ contact = data .contact [i ]
186+ if contact .dist < 0 : # Collision detected
187+ geom1 , geom2 = contact .geom1 , contact .geom2
188+
189+ if geom1 in obstacle_geom_indices or geom2 in obstacle_geom_indices :
190+ obstacle_collision_count += 1
191+ else :
192+ self_collision_count += 1
193+ viewer .sync ()
194+ collision_check_total_time += time .perf_counter () - start_check
195+ time .sleep (sleep_time )
196+
197+ elapsed_time = time .perf_counter () - start_time
198+
199+ avg_self_collisions = self_collision_count / num_samples
200+ avg_obstacle_collisions = obstacle_collision_count / num_samples
201+ avg_collision_check_time = collision_check_total_time / num_samples
202+ avg_elapsed_time = elapsed_time / num_samples
203+
204+ return avg_self_collisions , avg_obstacle_collisions , avg_elapsed_time , avg_collision_check_time
205+
206+ if __name__ == "__main__" :
207+ # Load obstacles from the URDF file and add them to both models
208+ obstacles_a_geoms = load_obstacles_from_urdf (spec_a , obstacles_urdf_path )
209+ obstacles_b_geoms = load_obstacles_from_urdf (spec_b , obstacles_urdf_path )
210+
211+ # Run the benchmark for both models
212+ print ("Benchmarking Model A (Complex)" )
213+ a_self_count , a_obst_count , a_total_time , a_check_time = benchmark_collision_checking (spec_a , obstacles_a_geoms )
214+ print ("Benchmarking Model B (Simple)" )
215+ b_self_count , b_obst_count , b_total_time , b_check_time = benchmark_collision_checking (spec_b , obstacles_b_geoms )
216+
217+ # Output the results
218+ print (f"\n Model A (Complex):" )
219+ print (f" Average Simulation Time: { a_total_time :.3f} sec" )
220+ print (f" Average Self-Collisions: { a_self_count } " )
221+ print (f" Average Obstacle Collisions: { a_obst_count } " )
222+ print (f" Average Collision Check Time: { a_check_time :.6f} sec" )
223+
224+ print (f"\n Model B (Simple):" )
225+ print (f" Average Simulation Time: { b_total_time :.3f} sec" )
226+ print (f" Average Self-Collisions: { b_self_count } " )
227+ print (f" Average Obstacle Collisions: { b_obst_count } " )
228+ print (f" Average Collision Check Time: { b_check_time :.6f} sec" )
0 commit comments