warp-lang 0.11.0__py3-none-manylinux2014_x86_64.whl → 1.0.0__py3-none-manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of warp-lang might be problematic. Click here for more details.

Files changed (170) hide show
  1. warp/__init__.py +8 -0
  2. warp/bin/warp-clang.so +0 -0
  3. warp/bin/warp.so +0 -0
  4. warp/build.py +7 -6
  5. warp/build_dll.py +70 -79
  6. warp/builtins.py +10 -6
  7. warp/codegen.py +51 -19
  8. warp/config.py +7 -8
  9. warp/constants.py +3 -0
  10. warp/context.py +948 -245
  11. warp/dlpack.py +198 -113
  12. warp/examples/assets/bunny.usd +0 -0
  13. warp/examples/assets/cartpole.urdf +110 -0
  14. warp/examples/assets/crazyflie.usd +0 -0
  15. warp/examples/assets/cube.usda +42 -0
  16. warp/examples/assets/nv_ant.xml +92 -0
  17. warp/examples/assets/nv_humanoid.xml +183 -0
  18. warp/examples/assets/quadruped.urdf +268 -0
  19. warp/examples/assets/rocks.nvdb +0 -0
  20. warp/examples/assets/rocks.usd +0 -0
  21. warp/examples/assets/sphere.usda +56 -0
  22. warp/examples/assets/torus.usda +105 -0
  23. warp/examples/benchmarks/benchmark_api.py +383 -0
  24. warp/examples/benchmarks/benchmark_cloth.py +279 -0
  25. warp/examples/benchmarks/benchmark_cloth_cupy.py +88 -0
  26. warp/examples/benchmarks/benchmark_cloth_jax.py +100 -0
  27. warp/examples/benchmarks/benchmark_cloth_numba.py +142 -0
  28. warp/examples/benchmarks/benchmark_cloth_numpy.py +77 -0
  29. warp/examples/benchmarks/benchmark_cloth_pytorch.py +86 -0
  30. warp/examples/benchmarks/benchmark_cloth_taichi.py +112 -0
  31. warp/examples/benchmarks/benchmark_cloth_warp.py +146 -0
  32. warp/examples/benchmarks/benchmark_launches.py +295 -0
  33. warp/examples/core/example_dem.py +221 -0
  34. warp/examples/core/example_fluid.py +267 -0
  35. warp/examples/core/example_graph_capture.py +129 -0
  36. warp/examples/core/example_marching_cubes.py +177 -0
  37. warp/examples/core/example_mesh.py +154 -0
  38. warp/examples/core/example_mesh_intersect.py +193 -0
  39. warp/examples/core/example_nvdb.py +169 -0
  40. warp/examples/core/example_raycast.py +89 -0
  41. warp/examples/core/example_raymarch.py +178 -0
  42. warp/examples/core/example_render_opengl.py +141 -0
  43. warp/examples/core/example_sph.py +389 -0
  44. warp/examples/core/example_torch.py +181 -0
  45. warp/examples/core/example_wave.py +249 -0
  46. warp/examples/fem/bsr_utils.py +380 -0
  47. warp/examples/fem/example_apic_fluid.py +391 -0
  48. warp/examples/fem/example_convection_diffusion.py +168 -0
  49. warp/examples/fem/example_convection_diffusion_dg.py +209 -0
  50. warp/examples/fem/example_convection_diffusion_dg0.py +194 -0
  51. warp/examples/fem/example_deformed_geometry.py +159 -0
  52. warp/examples/fem/example_diffusion.py +173 -0
  53. warp/examples/fem/example_diffusion_3d.py +152 -0
  54. warp/examples/fem/example_diffusion_mgpu.py +214 -0
  55. warp/examples/fem/example_mixed_elasticity.py +222 -0
  56. warp/examples/fem/example_navier_stokes.py +243 -0
  57. warp/examples/fem/example_stokes.py +192 -0
  58. warp/examples/fem/example_stokes_transfer.py +249 -0
  59. warp/examples/fem/mesh_utils.py +109 -0
  60. warp/examples/fem/plot_utils.py +287 -0
  61. warp/examples/optim/example_bounce.py +248 -0
  62. warp/examples/optim/example_cloth_throw.py +210 -0
  63. warp/examples/optim/example_diffray.py +535 -0
  64. warp/examples/optim/example_drone.py +850 -0
  65. warp/examples/optim/example_inverse_kinematics.py +169 -0
  66. warp/examples/optim/example_inverse_kinematics_torch.py +170 -0
  67. warp/examples/optim/example_spring_cage.py +234 -0
  68. warp/examples/optim/example_trajectory.py +201 -0
  69. warp/examples/sim/example_cartpole.py +128 -0
  70. warp/examples/sim/example_cloth.py +184 -0
  71. warp/examples/sim/example_granular.py +113 -0
  72. warp/examples/sim/example_granular_collision_sdf.py +185 -0
  73. warp/examples/sim/example_jacobian_ik.py +213 -0
  74. warp/examples/sim/example_particle_chain.py +106 -0
  75. warp/examples/sim/example_quadruped.py +179 -0
  76. warp/examples/sim/example_rigid_chain.py +191 -0
  77. warp/examples/sim/example_rigid_contact.py +176 -0
  78. warp/examples/sim/example_rigid_force.py +126 -0
  79. warp/examples/sim/example_rigid_gyroscopic.py +97 -0
  80. warp/examples/sim/example_rigid_soft_contact.py +124 -0
  81. warp/examples/sim/example_soft_body.py +178 -0
  82. warp/fabric.py +29 -20
  83. warp/fem/cache.py +0 -1
  84. warp/fem/dirichlet.py +0 -2
  85. warp/fem/integrate.py +0 -1
  86. warp/jax.py +45 -0
  87. warp/jax_experimental.py +339 -0
  88. warp/native/builtin.h +12 -0
  89. warp/native/bvh.cu +18 -18
  90. warp/native/clang/clang.cpp +8 -3
  91. warp/native/cuda_util.cpp +94 -5
  92. warp/native/cuda_util.h +35 -6
  93. warp/native/cutlass_gemm.cpp +1 -1
  94. warp/native/cutlass_gemm.cu +4 -1
  95. warp/native/error.cpp +66 -0
  96. warp/native/error.h +27 -0
  97. warp/native/mesh.cu +2 -2
  98. warp/native/reduce.cu +4 -4
  99. warp/native/runlength_encode.cu +2 -2
  100. warp/native/scan.cu +2 -2
  101. warp/native/sparse.cu +0 -1
  102. warp/native/temp_buffer.h +2 -2
  103. warp/native/warp.cpp +95 -60
  104. warp/native/warp.cu +1053 -218
  105. warp/native/warp.h +49 -32
  106. warp/optim/linear.py +33 -16
  107. warp/render/render_opengl.py +202 -101
  108. warp/render/render_usd.py +82 -40
  109. warp/sim/__init__.py +13 -4
  110. warp/sim/articulation.py +4 -5
  111. warp/sim/collide.py +320 -175
  112. warp/sim/import_mjcf.py +25 -30
  113. warp/sim/import_urdf.py +94 -63
  114. warp/sim/import_usd.py +51 -36
  115. warp/sim/inertia.py +3 -2
  116. warp/sim/integrator.py +233 -0
  117. warp/sim/integrator_euler.py +447 -469
  118. warp/sim/integrator_featherstone.py +1991 -0
  119. warp/sim/integrator_xpbd.py +1420 -640
  120. warp/sim/model.py +765 -487
  121. warp/sim/particles.py +2 -1
  122. warp/sim/render.py +35 -13
  123. warp/sim/utils.py +222 -11
  124. warp/stubs.py +8 -0
  125. warp/tape.py +16 -1
  126. warp/tests/aux_test_grad_customs.py +23 -0
  127. warp/tests/test_array.py +190 -1
  128. warp/tests/test_async.py +656 -0
  129. warp/tests/test_bool.py +50 -0
  130. warp/tests/test_dlpack.py +164 -11
  131. warp/tests/test_examples.py +166 -74
  132. warp/tests/test_fem.py +8 -1
  133. warp/tests/test_generics.py +15 -5
  134. warp/tests/test_grad.py +1 -1
  135. warp/tests/test_grad_customs.py +172 -12
  136. warp/tests/test_jax.py +254 -0
  137. warp/tests/test_large.py +29 -6
  138. warp/tests/test_launch.py +25 -0
  139. warp/tests/test_linear_solvers.py +20 -3
  140. warp/tests/test_matmul.py +61 -16
  141. warp/tests/test_matmul_lite.py +13 -13
  142. warp/tests/test_mempool.py +186 -0
  143. warp/tests/test_multigpu.py +3 -0
  144. warp/tests/test_options.py +16 -2
  145. warp/tests/test_peer.py +137 -0
  146. warp/tests/test_print.py +3 -1
  147. warp/tests/test_quat.py +23 -0
  148. warp/tests/test_sim_kinematics.py +97 -0
  149. warp/tests/test_snippet.py +126 -3
  150. warp/tests/test_streams.py +108 -79
  151. warp/tests/test_torch.py +16 -8
  152. warp/tests/test_utils.py +32 -27
  153. warp/tests/test_verify_fp.py +65 -0
  154. warp/tests/test_volume.py +1 -1
  155. warp/tests/unittest_serial.py +2 -0
  156. warp/tests/unittest_suites.py +12 -0
  157. warp/tests/unittest_utils.py +14 -7
  158. warp/thirdparty/unittest_parallel.py +15 -3
  159. warp/torch.py +10 -8
  160. warp/types.py +363 -246
  161. warp/utils.py +143 -19
  162. warp_lang-1.0.0.dist-info/LICENSE.md +126 -0
  163. warp_lang-1.0.0.dist-info/METADATA +394 -0
  164. {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/RECORD +167 -86
  165. warp/sim/optimizer.py +0 -138
  166. warp_lang-0.11.0.dist-info/LICENSE.md +0 -36
  167. warp_lang-0.11.0.dist-info/METADATA +0 -238
  168. /warp/tests/{walkthough_debug.py → walkthrough_debug.py} +0 -0
  169. {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/WHEEL +0 -0
  170. {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,850 @@
1
+ # Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved.
2
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
3
+ # and proprietary rights in and to this software, related documentation
4
+ # and any modifications thereto. Any use, reproduction, disclosure or
5
+ # distribution of this software and related documentation without an express
6
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
7
+
8
+ ###########################################################################
9
+ # Example Drone
10
+ #
11
+ # A drone and its 4 propellers is simulated with the goal of reaching
12
+ # different targets via model-predictive control (MPC) that continuously
13
+ # optimizes the control trajectory.
14
+ #
15
+ ###########################################################################
16
+
17
+ import os
18
+ from typing import Optional, Tuple
19
+
20
+ import numpy as np
21
+
22
+ import warp as wp
23
+ import warp.optim
24
+ import warp.sim
25
+ from warp.sim.collide import (
26
+ box_sdf,
27
+ capsule_sdf,
28
+ cone_sdf,
29
+ cylinder_sdf,
30
+ mesh_sdf,
31
+ plane_sdf,
32
+ sphere_sdf,
33
+ )
34
+
35
+ wp.init()
36
+
37
+
38
+ @wp.struct
39
+ class Propeller:
40
+ body: int
41
+ pos: wp.vec3
42
+ dir: wp.vec3
43
+ thrust: float
44
+ power: float
45
+ diameter: float
46
+ height: float
47
+ max_rpm: float
48
+ max_thrust: float
49
+ max_torque: float
50
+ turning_direction: float
51
+ max_speed_square: float
52
+
53
+
54
+ @wp.kernel
55
+ def increment_seed(
56
+ seed: wp.array(dtype=int),
57
+ ):
58
+ seed[0] += 1
59
+
60
+
61
+ @wp.kernel
62
+ def sample_gaussian(
63
+ mean_trajectory: wp.array(dtype=float, ndim=3),
64
+ noise_scale: float,
65
+ num_control_points: int,
66
+ control_dim: int,
67
+ control_limits: wp.array(dtype=float, ndim=2),
68
+ seed: wp.array(dtype=int),
69
+ rollout_trajectories: wp.array(dtype=float, ndim=3),
70
+ ):
71
+ env_id, point_id, control_id = wp.tid()
72
+ unique_id = (env_id * num_control_points + point_id) * control_dim + control_id
73
+ r = wp.rand_init(seed[0], unique_id)
74
+ mean = mean_trajectory[0, point_id, control_id]
75
+ lo, hi = control_limits[control_id, 0], control_limits[control_id, 1]
76
+ sample = mean + noise_scale * wp.randn(r)
77
+ for i in range(10):
78
+ if sample < lo or sample > hi:
79
+ sample = mean + noise_scale * wp.randn(r)
80
+ else:
81
+ break
82
+ rollout_trajectories[env_id, point_id, control_id] = wp.clamp(sample, lo, hi)
83
+
84
+
85
+ @wp.kernel
86
+ def replicate_states(
87
+ body_q_in: wp.array(dtype=wp.transform),
88
+ body_qd_in: wp.array(dtype=wp.spatial_vector),
89
+ bodies_per_env: int,
90
+ body_q_out: wp.array(dtype=wp.transform),
91
+ body_qd_out: wp.array(dtype=wp.spatial_vector),
92
+ ):
93
+ tid = wp.tid()
94
+ env_offset = tid * bodies_per_env
95
+ for i in range(bodies_per_env):
96
+ body_q_out[env_offset + i] = body_q_in[i]
97
+ body_qd_out[env_offset + i] = body_qd_in[i]
98
+
99
+
100
+ @wp.kernel
101
+ def drone_cost(
102
+ body_q: wp.array(dtype=wp.transform),
103
+ body_qd: wp.array(dtype=wp.spatial_vector),
104
+ target: wp.vec3,
105
+ prop_control: wp.array(dtype=float),
106
+ step: int,
107
+ horizon_length: int,
108
+ weighting: float,
109
+ cost: wp.array(dtype=wp.float32),
110
+ ):
111
+ env_id = wp.tid()
112
+ tf = body_q[env_id]
113
+
114
+ pos_drone = wp.transform_get_translation(tf)
115
+ pos_cost = wp.length_sq(pos_drone - target)
116
+ altitude_cost = wp.max(pos_drone[1] - 0.75, 0.0) + wp.max(0.25 - pos_drone[1], 0.0)
117
+ upvector = wp.vec3(0.0, 1.0, 0.0)
118
+ drone_up = wp.transform_vector(tf, upvector)
119
+ upright_cost = 1.0 - wp.dot(drone_up, upvector)
120
+
121
+ vel_drone = body_qd[env_id]
122
+
123
+ # Encourage zero velocity.
124
+ vel_cost = wp.length_sq(vel_drone)
125
+
126
+ control = wp.vec4(
127
+ prop_control[env_id * 4 + 0],
128
+ prop_control[env_id * 4 + 1],
129
+ prop_control[env_id * 4 + 2],
130
+ prop_control[env_id * 4 + 3],
131
+ )
132
+ control_cost = wp.dot(control, control)
133
+
134
+ discount = 0.8 ** wp.float(horizon_length - step - 1) / wp.float(horizon_length) ** 2.0
135
+
136
+ pos_weight = 1000.0
137
+ altitude_weight = 100.0
138
+ control_weight = 0.05
139
+ vel_weight = 0.1
140
+ upright_weight = 10.0
141
+ total_weight = pos_weight + altitude_weight + control_weight + vel_weight + upright_weight
142
+
143
+ wp.atomic_add(
144
+ cost,
145
+ env_id,
146
+ (
147
+ pos_cost * pos_weight
148
+ + altitude_cost * altitude_weight
149
+ + control_cost * control_weight
150
+ + vel_cost * vel_weight
151
+ + upright_cost * upright_weight
152
+ )
153
+ * (weighting / total_weight)
154
+ * discount,
155
+ )
156
+
157
+
158
+ @wp.kernel
159
+ def collision_cost(
160
+ body_q: wp.array(dtype=wp.transform),
161
+ obstacle_ids: wp.array(dtype=int, ndim=2),
162
+ shape_X_bs: wp.array(dtype=wp.transform),
163
+ geo: wp.sim.ModelShapeGeometry,
164
+ margin: float,
165
+ weighting: float,
166
+ cost: wp.array(dtype=wp.float32),
167
+ ):
168
+ env_id, obs_id = wp.tid()
169
+ shape_index = obstacle_ids[env_id, obs_id]
170
+
171
+ px = wp.transform_get_translation(body_q[env_id])
172
+
173
+ X_bs = shape_X_bs[shape_index]
174
+
175
+ # transform particle position to shape local space
176
+ x_local = wp.transform_point(X_bs, px)
177
+
178
+ # geo description
179
+ geo_type = geo.type[shape_index]
180
+ geo_scale = geo.scale[shape_index]
181
+
182
+ # evaluate shape sdf
183
+ d = 1e6
184
+
185
+ if geo_type == wp.sim.GEO_SPHERE:
186
+ d = sphere_sdf(wp.vec3(), geo_scale[0], x_local)
187
+ elif geo_type == wp.sim.GEO_BOX:
188
+ d = box_sdf(geo_scale, x_local)
189
+ elif geo_type == wp.sim.GEO_CAPSULE:
190
+ d = capsule_sdf(geo_scale[0], geo_scale[1], x_local)
191
+ elif geo_type == wp.sim.GEO_CYLINDER:
192
+ d = cylinder_sdf(geo_scale[0], geo_scale[1], x_local)
193
+ elif geo_type == wp.sim.GEO_CONE:
194
+ d = cone_sdf(geo_scale[0], geo_scale[1], x_local)
195
+ elif geo_type == wp.sim.GEO_MESH:
196
+ mesh = geo.source[shape_index]
197
+ min_scale = wp.min(geo_scale)
198
+ max_dist = margin / min_scale
199
+ d = mesh_sdf(mesh, wp.cw_div(x_local, geo_scale), max_dist)
200
+ d *= min_scale # TODO fix this, mesh scaling needs to be handled properly
201
+ elif geo_type == wp.sim.GEO_SDF:
202
+ volume = geo.source[shape_index]
203
+ xpred_local = wp.volume_world_to_index(volume, wp.cw_div(x_local, geo_scale))
204
+ nn = wp.vec3(0.0, 0.0, 0.0)
205
+ d = wp.volume_sample_grad_f(volume, xpred_local, wp.Volume.LINEAR, nn)
206
+ elif geo_type == wp.sim.GEO_PLANE:
207
+ d = plane_sdf(geo_scale[0], geo_scale[1], x_local)
208
+
209
+ d = wp.max(d, 0.0)
210
+ if d < margin:
211
+ c = margin - d
212
+ wp.atomic_add(cost, env_id, weighting * c)
213
+
214
+
215
+ @wp.kernel
216
+ def enforce_control_limits(
217
+ control_limits: wp.array(dtype=float, ndim=2),
218
+ control_points: wp.array(dtype=float, ndim=3),
219
+ ):
220
+ env_id, t_id, control_id = wp.tid()
221
+ lo, hi = control_limits[control_id, 0], control_limits[control_id, 1]
222
+ control_points[env_id, t_id, control_id] = wp.clamp(control_points[env_id, t_id, control_id], lo, hi)
223
+
224
+
225
+ @wp.kernel
226
+ def pick_best_trajectory(
227
+ rollout_trajectories: wp.array(dtype=float, ndim=3),
228
+ lowest_cost_id: int,
229
+ best_traj: wp.array(dtype=float, ndim=3),
230
+ ):
231
+ t_id, control_id = wp.tid()
232
+ best_traj[0, t_id, control_id] = rollout_trajectories[lowest_cost_id, t_id, control_id]
233
+
234
+
235
+ @wp.kernel
236
+ def interpolate_control_linear(
237
+ control_points: wp.array(dtype=float, ndim=3),
238
+ control_dofs: wp.array(dtype=int),
239
+ control_gains: wp.array(dtype=float),
240
+ t: float,
241
+ torque_dim: int,
242
+ torques: wp.array(dtype=float),
243
+ ):
244
+ env_id, control_id = wp.tid()
245
+ t_id = int(t)
246
+ frac = t - wp.floor(t)
247
+ control_left = control_points[env_id, t_id, control_id]
248
+ control_right = control_points[env_id, t_id + 1, control_id]
249
+ torque_id = env_id * torque_dim + control_dofs[control_id]
250
+ action = control_left * (1.0 - frac) + control_right * frac
251
+ torques[torque_id] = action * control_gains[control_id]
252
+
253
+
254
+ @wp.kernel
255
+ def compute_prop_wrenches(
256
+ props: wp.array(dtype=Propeller),
257
+ controls: wp.array(dtype=float),
258
+ body_q: wp.array(dtype=wp.transform),
259
+ body_com: wp.array(dtype=wp.vec3),
260
+ body_f: wp.array(dtype=wp.spatial_vector),
261
+ ):
262
+ tid = wp.tid()
263
+ prop = props[tid]
264
+ control = controls[tid]
265
+ tf = body_q[prop.body]
266
+ dir = wp.transform_vector(tf, prop.dir)
267
+ force = dir * prop.max_thrust * control
268
+ torque = dir * prop.max_torque * control * prop.turning_direction
269
+ moment_arm = wp.transform_point(tf, prop.pos) - wp.transform_point(tf, body_com[prop.body])
270
+ torque += wp.cross(moment_arm, force)
271
+ # Apply angular damping.
272
+ torque *= 0.8
273
+ wp.atomic_add(body_f, prop.body, wp.spatial_vector(torque, force))
274
+
275
+
276
+ def define_propeller(
277
+ drone: int,
278
+ pos: wp.vec3,
279
+ fps: float,
280
+ thrust: float = 0.109919,
281
+ power: float = 0.040164,
282
+ diameter: float = 0.2286,
283
+ height: float = 0.01,
284
+ max_rpm: float = 6396.667,
285
+ turning_direction: float = 1.0,
286
+ ):
287
+ # Air density at sea level.
288
+ air_density = 1.225 # kg / m^3
289
+
290
+ rps = max_rpm / fps
291
+ max_speed = rps * wp.TAU # radians / sec
292
+ rps_square = rps**2
293
+
294
+ prop = Propeller()
295
+ prop.body = drone
296
+ prop.pos = pos
297
+ prop.dir = wp.vec3(0.0, 1.0, 0.0)
298
+ prop.thrust = thrust
299
+ prop.power = power
300
+ prop.diameter = diameter
301
+ prop.height = height
302
+ prop.max_rpm = max_rpm
303
+ prop.max_thrust = thrust * air_density * rps_square * diameter**4
304
+ prop.max_torque = power * air_density * rps_square * diameter**5 / wp.TAU
305
+ prop.turning_direction = turning_direction
306
+ prop.max_speed_square = max_speed**2
307
+
308
+ return prop
309
+
310
+
311
+ class Drone:
312
+ def __init__(
313
+ self,
314
+ name: str,
315
+ fps: float,
316
+ trajectory_shape: Tuple[int, int],
317
+ variation_count: int = 1,
318
+ size: float = 1.0,
319
+ requires_grad: bool = False,
320
+ state_count: Optional[int] = None,
321
+ ) -> None:
322
+ self.variation_count = variation_count
323
+ self.requires_grad = requires_grad
324
+
325
+ # Current tick of the simulation, including substeps.
326
+ self.sim_tick = 0
327
+
328
+ # Initialize the helper to build a physics scene.
329
+ builder = wp.sim.ModelBuilder()
330
+ builder.rigid_contact_margin = 0.05
331
+
332
+ # Initialize the rigid bodies, propellers, and colliders.
333
+ props = []
334
+ colliders = []
335
+ crossbar_length = size
336
+ crossbar_height = size * 0.05
337
+ crossbar_width = size * 0.05
338
+ carbon_fiber_density = 1750.0 # kg / m^3
339
+ for i in range(variation_count):
340
+ # Register the drone as a rigid body in the simulation model.
341
+ body = builder.add_body(name=f"{name}_{i}")
342
+
343
+ # Define the shapes making up the drone's rigid body.
344
+ builder.add_shape_box(
345
+ body,
346
+ hx=crossbar_length,
347
+ hy=crossbar_height,
348
+ hz=crossbar_width,
349
+ density=carbon_fiber_density,
350
+ collision_group=i,
351
+ )
352
+ builder.add_shape_box(
353
+ body,
354
+ hx=crossbar_width,
355
+ hy=crossbar_height,
356
+ hz=crossbar_length,
357
+ density=carbon_fiber_density,
358
+ collision_group=i,
359
+ )
360
+
361
+ # Initialize the propellers.
362
+ props.extend(
363
+ (
364
+ define_propeller(
365
+ body,
366
+ wp.vec3(crossbar_length, 0.0, 0.0),
367
+ fps,
368
+ turning_direction=-1.0,
369
+ ),
370
+ define_propeller(
371
+ body,
372
+ wp.vec3(-crossbar_length, 0.0, 0.0),
373
+ fps,
374
+ turning_direction=1.0,
375
+ ),
376
+ define_propeller(
377
+ body,
378
+ wp.vec3(0.0, 0.0, crossbar_length),
379
+ fps,
380
+ turning_direction=1.0,
381
+ ),
382
+ define_propeller(
383
+ body,
384
+ wp.vec3(0.0, 0.0, -crossbar_length),
385
+ fps,
386
+ turning_direction=-1.0,
387
+ ),
388
+ ),
389
+ )
390
+
391
+ # Initialize the colliders.
392
+ colliders.append(
393
+ (
394
+ builder.add_shape_capsule(
395
+ -1,
396
+ pos=(0.5, 2.0, 0.5),
397
+ radius=0.15,
398
+ half_height=2.0,
399
+ collision_group=i,
400
+ ),
401
+ builder.add_shape_capsule(
402
+ -1,
403
+ pos=(-0.5, 2.0, -0.5),
404
+ radius=0.15,
405
+ half_height=2.0,
406
+ collision_group=i,
407
+ ),
408
+ ),
409
+ )
410
+ self.props = wp.array(props, dtype=Propeller)
411
+ self.colliders = wp.array(colliders, dtype=int)
412
+
413
+ # Build the model and set-up its properties.
414
+ self.model = builder.finalize(requires_grad=requires_grad)
415
+ self.model.ground = False
416
+
417
+ # Initialize the required simulation states.
418
+ if requires_grad:
419
+ self.states = tuple(self.model.state() for _ in range(state_count + 1))
420
+ self.controls = tuple(self.model.control() for _ in range(state_count))
421
+ else:
422
+ # When only running a forward simulation, we don't need to store
423
+ # the history of the states at each step, instead we use double
424
+ # buffering to represent the previous and next states.
425
+ self.states = [self.model.state(), self.model.state()]
426
+ self.controls = (self.model.control(),)
427
+
428
+ # create array for the propeller controls
429
+ for control in self.controls:
430
+ control.prop_controls = wp.zeros(len(self.props), dtype=float, requires_grad=requires_grad)
431
+
432
+ # Define the trajectories as arrays of control points.
433
+ # The point data has an additional item to support linear interpolation.
434
+ self.trajectories = wp.zeros(
435
+ (variation_count, trajectory_shape[0], trajectory_shape[1]),
436
+ dtype=float,
437
+ requires_grad=requires_grad,
438
+ )
439
+
440
+ # Store some miscellaneous info.
441
+ self.body_count = len(builder.body_q)
442
+ self.collider_count = self.colliders.shape[1]
443
+ self.collision_radius = crossbar_length * 2.0
444
+
445
+ @property
446
+ def state(self) -> wp.sim.State:
447
+ return self.states[self.sim_tick if self.requires_grad else 0]
448
+
449
+ @property
450
+ def next_state(self) -> wp.sim.State:
451
+ return self.states[self.sim_tick + 1 if self.requires_grad else 1]
452
+
453
+ @property
454
+ def control(self) -> wp.sim.Control:
455
+ return self.controls[min(len(self.controls) - 1, self.sim_tick) if self.requires_grad else 0]
456
+
457
+
458
+ class Example:
459
+ def __init__(
460
+ self,
461
+ stage: Optional[str] = None,
462
+ drone_path: Optional[str] = None,
463
+ enable_rendering: bool = True,
464
+ render_rollouts: bool = True,
465
+ verbose: bool = False,
466
+ ) -> None:
467
+ # Duration of the simulation, in seconds.
468
+ duration = 15.0
469
+
470
+ # Number of frames per second.
471
+ self.fps = 60.0
472
+
473
+ # Duration of the simulation in number of frames.
474
+ self.frame_count = int(duration * self.fps)
475
+
476
+ # Number of simulation substeps to take per step.
477
+ self.sim_substep_count = 1
478
+
479
+ # Delta time between each simulation substep.
480
+ self.frame_dt = 1.0 / self.fps
481
+
482
+ # Delta time between each simulation substep.
483
+ self.sim_dt = self.frame_dt / self.sim_substep_count
484
+
485
+ # Frame number used for simulation and rendering.
486
+ self.frame = 0
487
+
488
+ # Targets positions that the drone will try to reach in turn.
489
+ self.targets = (
490
+ wp.vec3(0.0, 0.5, 1.0),
491
+ wp.vec3(1.0, 0.5, 0.0),
492
+ wp.vec3(0.0, 0.5, -1.0),
493
+ wp.vec3(-1.0, 0.5, 0.0),
494
+ )
495
+
496
+ # Define the index of the active target.
497
+ # We start with -1 since it'll be incremented on the first frame.
498
+ self.target_idx = -1
499
+
500
+ # Number of steps to run at each frame for the optimisation pass.
501
+ self.optim_step_count = 20
502
+
503
+ # Time steps between control points.
504
+ self.control_point_step = 10
505
+
506
+ # Number of control horizon points to interpolate between.
507
+ self.control_point_count = 3
508
+
509
+ self.control_point_data_count = self.control_point_count + 1
510
+ self.control_dofs = wp.array((0, 1, 2, 3), dtype=int)
511
+ self.control_dim = len(self.control_dofs)
512
+ self.control_gains = wp.array((0.8,) * self.control_dim, dtype=float)
513
+ self.control_limits = wp.array(((0.1, 1.0),) * self.control_dim, dtype=float)
514
+
515
+ drone_size = 0.2
516
+
517
+ # Declare the reference drone.
518
+ self.drone = Drone(
519
+ "drone",
520
+ self.fps,
521
+ (self.control_point_data_count, self.control_dim),
522
+ size=drone_size,
523
+ )
524
+
525
+ # Declare the drone's rollouts.
526
+ # These allow to run parallel simulations in order to find the best
527
+ # trajectory at each control point.
528
+ self.rollout_count = 16
529
+ self.rollout_step_count = self.control_point_step * self.control_point_count
530
+ self.rollouts = Drone(
531
+ "rollout",
532
+ self.fps,
533
+ (self.control_point_data_count, self.control_dim),
534
+ variation_count=self.rollout_count,
535
+ size=drone_size,
536
+ requires_grad=True,
537
+ state_count=self.rollout_step_count * self.sim_substep_count,
538
+ )
539
+
540
+ self.seed = wp.zeros(1, dtype=int)
541
+ self.rollout_costs = wp.zeros(self.rollout_count, dtype=float, requires_grad=True)
542
+
543
+ # Use the Euler integrator for stepping through the simulation.
544
+ self.integrator = wp.sim.SemiImplicitIntegrator()
545
+
546
+ self.optimizer = wp.optim.SGD(
547
+ [self.rollouts.trajectories.flatten()],
548
+ lr=1e-2,
549
+ nesterov=False,
550
+ momentum=0.0,
551
+ )
552
+
553
+ self.tape = None
554
+
555
+ if enable_rendering:
556
+ from pxr import UsdGeom
557
+
558
+ import warp.sim.render
559
+
560
+ # Helper to render the physics scene as a USD file.
561
+ self.renderer = wp.sim.render.SimRenderer(self.drone.model, stage, fps=self.fps)
562
+
563
+ # Remove the default drone geometries.
564
+ drone_root_prim = self.renderer.stage.GetPrimAtPath("/root/body_0_drone_0")
565
+ for prim in drone_root_prim.GetChildren():
566
+ self.renderer.stage.RemovePrim(prim.GetPath())
567
+
568
+ # Add a reference to the drone geometry.
569
+ drone_prim = self.renderer.stage.OverridePrim(f"{drone_root_prim.GetPath()}/crazyflie")
570
+ drone_prim.GetReferences().AddReference(drone_path)
571
+ drone_xform = UsdGeom.Xform(drone_prim)
572
+ drone_xform.AddTranslateOp().Set((0.0, -0.05, 0.0))
573
+ drone_xform.AddRotateYOp().Set(45.0)
574
+ drone_xform.AddScaleOp().Set((drone_size * 0.2,) * 3)
575
+
576
+ # Get the propellers to spin
577
+ for turning_direction in ("CW", "CCW"):
578
+ spin = 100.0 * 360.0 * self.frame_count / self.fps
579
+ spin = spin if turning_direction == "CCW" else -spin
580
+ for side in ("Back", "Front"):
581
+ prop_prim = self.renderer.stage.OverridePrim(
582
+ f"{drone_prim.GetPath()}/Propeller{turning_direction}{side}"
583
+ )
584
+ prop_xform = UsdGeom.Xform(prop_prim)
585
+ rot = prop_xform.AddRotateYOp()
586
+ rot.Set(0.0, 0.0)
587
+ rot.Set(spin, self.frame_count)
588
+ else:
589
+ self.renderer = None
590
+
591
+ self.use_cuda_graph = True
592
+ self.optim_graph = None
593
+
594
+ self.render_rollouts = render_rollouts
595
+ self.verbose = verbose
596
+
597
+ def update_drone(self, drone: Drone) -> None:
598
+ drone.state.clear_forces()
599
+
600
+ wp.launch(
601
+ interpolate_control_linear,
602
+ dim=(
603
+ drone.variation_count,
604
+ self.control_dim,
605
+ ),
606
+ inputs=(
607
+ drone.trajectories,
608
+ self.control_dofs,
609
+ self.control_gains,
610
+ drone.sim_tick / (self.sim_substep_count * self.control_point_step),
611
+ self.control_dim,
612
+ ),
613
+ outputs=(drone.control.prop_controls,),
614
+ )
615
+
616
+ wp.sim.collide(drone.model, drone.state)
617
+
618
+ wp.launch(
619
+ compute_prop_wrenches,
620
+ dim=len(drone.props),
621
+ inputs=(
622
+ drone.props,
623
+ drone.control.prop_controls,
624
+ drone.state.body_q,
625
+ drone.model.body_com,
626
+ ),
627
+ outputs=(drone.state.body_f,),
628
+ )
629
+
630
+ self.integrator.simulate(
631
+ drone.model,
632
+ drone.state,
633
+ drone.next_state,
634
+ self.sim_dt,
635
+ drone.control,
636
+ )
637
+
638
+ drone.sim_tick += 1
639
+
640
+ def forward(self):
641
+ # Evaluate the rollouts with their costs.
642
+ self.rollouts.sim_tick = 0
643
+ self.rollout_costs.zero_()
644
+ wp.launch(
645
+ replicate_states,
646
+ dim=self.rollout_count,
647
+ inputs=(
648
+ self.drone.state.body_q,
649
+ self.drone.state.body_qd,
650
+ self.drone.body_count,
651
+ ),
652
+ outputs=(
653
+ self.rollouts.state.body_q,
654
+ self.rollouts.state.body_qd,
655
+ ),
656
+ )
657
+
658
+ for i in range(self.rollout_step_count):
659
+ for _ in range(self.sim_substep_count):
660
+ self.update_drone(self.rollouts)
661
+
662
+ wp.launch(
663
+ drone_cost,
664
+ dim=self.rollout_count,
665
+ inputs=(
666
+ self.rollouts.state.body_q,
667
+ self.rollouts.state.body_qd,
668
+ self.targets[self.target_idx],
669
+ self.rollouts.control.prop_controls,
670
+ i,
671
+ self.rollout_step_count,
672
+ 1e3,
673
+ ),
674
+ outputs=(self.rollout_costs,),
675
+ )
676
+ wp.launch(
677
+ collision_cost,
678
+ dim=(
679
+ self.rollout_count,
680
+ self.rollouts.collider_count,
681
+ ),
682
+ inputs=(
683
+ self.rollouts.state.body_q,
684
+ self.rollouts.colliders,
685
+ self.rollouts.model.shape_transform,
686
+ self.rollouts.model.shape_geo,
687
+ self.rollouts.collision_radius,
688
+ 1e4,
689
+ ),
690
+ outputs=(self.rollout_costs,),
691
+ )
692
+
693
+ def step_optimizer(self):
694
+ if self.optim_graph is None:
695
+ self.tape = wp.Tape()
696
+ with self.tape:
697
+ self.forward()
698
+ self.rollout_costs.grad.fill_(1.0)
699
+ self.tape.backward()
700
+ else:
701
+ wp.capture_launch(self.optim_graph)
702
+
703
+ self.optimizer.step([self.rollouts.trajectories.grad.flatten()])
704
+
705
+ # Enforce limits on the control points.
706
+ wp.launch(
707
+ enforce_control_limits,
708
+ dim=self.rollouts.trajectories.shape,
709
+ inputs=(self.control_limits,),
710
+ outputs=(self.rollouts.trajectories,),
711
+ )
712
+ self.tape.zero()
713
+
714
+ def step(self):
715
+ if int((self.frame_count / len(self.targets))) == 0:
716
+ self.target_idx += 1
717
+
718
+ # Force recapturing the CUDA graph for the optimization pass
719
+ # by invalidating it.
720
+ self.optim_graph = None
721
+
722
+ if self.use_cuda_graph and self.optim_graph is None:
723
+ with wp.ScopedCapture() as capture:
724
+ self.tape = wp.Tape()
725
+ with self.tape:
726
+ self.forward()
727
+ self.rollout_costs.grad.fill_(1.0)
728
+ self.tape.backward()
729
+ self.optim_graph = capture.graph
730
+
731
+ # Sample control waypoints around the nominal trajectory.
732
+ self.seed.zero_()
733
+ noise_scale = 0.15
734
+ wp.launch(
735
+ sample_gaussian,
736
+ dim=(
737
+ self.rollouts.trajectories.shape[0] - 1,
738
+ self.rollouts.trajectories.shape[1],
739
+ self.rollouts.trajectories.shape[2],
740
+ ),
741
+ inputs=(
742
+ self.drone.trajectories,
743
+ noise_scale,
744
+ self.control_point_data_count,
745
+ self.control_dim,
746
+ self.control_limits,
747
+ self.seed,
748
+ ),
749
+ outputs=(self.rollouts.trajectories,),
750
+ )
751
+
752
+ wp.launch(
753
+ increment_seed,
754
+ dim=1,
755
+ inputs=(),
756
+ outputs=(self.seed,),
757
+ )
758
+
759
+ for _ in range(self.optim_step_count):
760
+ self.step_optimizer()
761
+
762
+ # Pick the best trajectory.
763
+ wp.synchronize()
764
+ lowest_cost_id = np.argmin(self.rollout_costs.numpy())
765
+ wp.launch(
766
+ pick_best_trajectory,
767
+ dim=(
768
+ self.control_point_data_count,
769
+ self.control_dim,
770
+ ),
771
+ inputs=(
772
+ self.rollouts.trajectories,
773
+ lowest_cost_id,
774
+ ),
775
+ outputs=(self.drone.trajectories,),
776
+ )
777
+ self.rollouts.trajectories[-1].assign(self.drone.trajectories[0])
778
+
779
+ # Simulate the drone.
780
+ self.drone.sim_tick = 0
781
+ for _ in range(self.sim_substep_count):
782
+ self.update_drone(self.drone)
783
+
784
+ # Swap the drone's states.
785
+ (self.drone.states[0], self.drone.states[1]) = (self.drone.states[1], self.drone.states[0])
786
+
787
+ def render(self):
788
+ if self.renderer is None:
789
+ return
790
+
791
+ self.renderer.begin_frame(self.frame / self.fps)
792
+ self.renderer.render(self.drone.state)
793
+
794
+ # Render a sphere as the current target.
795
+ self.renderer.render_sphere(
796
+ "target",
797
+ self.targets[self.target_idx],
798
+ wp.quat_identity(),
799
+ 0.05,
800
+ color=(1.0, 0.0, 0.0),
801
+ )
802
+
803
+ # Render the rollout trajectories.
804
+ if self.render_rollouts:
805
+ costs = self.rollout_costs.numpy()
806
+
807
+ positions = np.array([x.body_q.numpy()[:, :3] for x in self.rollouts.states])
808
+
809
+ min_cost = np.min(costs)
810
+ max_cost = np.max(costs)
811
+ for i in range(self.rollout_count):
812
+ # Flip colors, so red means best trajectory, blue worst.
813
+ color = wp.render.bourke_color_map(-max_cost, -min_cost, -costs[i])
814
+ self.renderer.render_line_strip(
815
+ name=f"rollout_{i}",
816
+ vertices=positions[:, i],
817
+ color=color,
818
+ radius=0.001,
819
+ )
820
+
821
+ self.renderer.end_frame()
822
+
823
+ self.frame += 1
824
+
825
+
826
+ if __name__ == "__main__":
827
+ this_dir = os.path.realpath(os.path.dirname(__file__))
828
+ stage_path = os.path.join(this_dir, "example_drone.usd")
829
+ drone_path = os.path.join(this_dir, "..", "assets", "crazyflie.usd")
830
+
831
+ example = Example(stage_path, drone_path, verbose=True)
832
+ for i in range(example.frame_count):
833
+ if i > 0 and i % int((example.frame_count / len(example.targets))) == 0:
834
+ example.target_idx += 1
835
+ if example.verbose:
836
+ print(f"Choosing new flight target: {example.target_idx + 1}")
837
+
838
+ # Force recapturing the CUDA graph for the optimisation pass
839
+ # by invalidating it.
840
+ example.optim_graph = None
841
+
842
+ example.step()
843
+ example.render()
844
+
845
+ if example.verbose:
846
+ loss = np.min(example.rollout_costs.numpy())
847
+ print(f"[{i+1:3d}/{example.frame_count}] loss={loss:.8f}")
848
+
849
+ if example.renderer is not None:
850
+ example.renderer.save()