warp-lang 1.8.0__py3-none-win_amd64.whl → 1.9.0__py3-none-win_amd64.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 (153) hide show
  1. warp/__init__.py +282 -103
  2. warp/__init__.pyi +482 -110
  3. warp/bin/warp-clang.dll +0 -0
  4. warp/bin/warp.dll +0 -0
  5. warp/build.py +93 -30
  6. warp/build_dll.py +48 -63
  7. warp/builtins.py +955 -137
  8. warp/codegen.py +327 -209
  9. warp/config.py +1 -1
  10. warp/context.py +1363 -800
  11. warp/examples/core/example_marching_cubes.py +1 -0
  12. warp/examples/core/example_render_opengl.py +100 -3
  13. warp/examples/fem/example_apic_fluid.py +98 -52
  14. warp/examples/fem/example_convection_diffusion_dg.py +25 -4
  15. warp/examples/fem/example_diffusion_mgpu.py +8 -3
  16. warp/examples/fem/utils.py +68 -22
  17. warp/examples/interop/example_jax_callable.py +34 -4
  18. warp/examples/interop/example_jax_kernel.py +27 -1
  19. warp/fabric.py +1 -1
  20. warp/fem/cache.py +27 -19
  21. warp/fem/domain.py +2 -2
  22. warp/fem/field/nodal_field.py +2 -2
  23. warp/fem/field/virtual.py +266 -166
  24. warp/fem/geometry/geometry.py +5 -5
  25. warp/fem/integrate.py +200 -91
  26. warp/fem/space/restriction.py +4 -0
  27. warp/fem/space/shape/tet_shape_function.py +3 -10
  28. warp/jax_experimental/custom_call.py +1 -1
  29. warp/jax_experimental/ffi.py +203 -54
  30. warp/marching_cubes.py +708 -0
  31. warp/native/array.h +103 -8
  32. warp/native/builtin.h +90 -9
  33. warp/native/bvh.cpp +64 -28
  34. warp/native/bvh.cu +58 -58
  35. warp/native/bvh.h +2 -2
  36. warp/native/clang/clang.cpp +7 -7
  37. warp/native/coloring.cpp +13 -3
  38. warp/native/crt.cpp +2 -2
  39. warp/native/crt.h +3 -5
  40. warp/native/cuda_util.cpp +42 -11
  41. warp/native/cuda_util.h +10 -4
  42. warp/native/exports.h +1842 -1908
  43. warp/native/fabric.h +2 -1
  44. warp/native/hashgrid.cpp +37 -37
  45. warp/native/hashgrid.cu +2 -2
  46. warp/native/initializer_array.h +1 -1
  47. warp/native/intersect.h +4 -4
  48. warp/native/mat.h +1913 -119
  49. warp/native/mathdx.cpp +43 -43
  50. warp/native/mesh.cpp +24 -24
  51. warp/native/mesh.cu +26 -26
  52. warp/native/mesh.h +5 -3
  53. warp/native/nanovdb/GridHandle.h +179 -12
  54. warp/native/nanovdb/HostBuffer.h +8 -7
  55. warp/native/nanovdb/NanoVDB.h +517 -895
  56. warp/native/nanovdb/NodeManager.h +323 -0
  57. warp/native/nanovdb/PNanoVDB.h +2 -2
  58. warp/native/quat.h +337 -16
  59. warp/native/rand.h +7 -7
  60. warp/native/range.h +7 -1
  61. warp/native/reduce.cpp +10 -10
  62. warp/native/reduce.cu +13 -14
  63. warp/native/runlength_encode.cpp +2 -2
  64. warp/native/runlength_encode.cu +5 -5
  65. warp/native/scan.cpp +3 -3
  66. warp/native/scan.cu +4 -4
  67. warp/native/sort.cpp +10 -10
  68. warp/native/sort.cu +22 -22
  69. warp/native/sparse.cpp +8 -8
  70. warp/native/sparse.cu +14 -14
  71. warp/native/spatial.h +366 -17
  72. warp/native/svd.h +23 -8
  73. warp/native/temp_buffer.h +2 -2
  74. warp/native/tile.h +303 -70
  75. warp/native/tile_radix_sort.h +5 -1
  76. warp/native/tile_reduce.h +16 -25
  77. warp/native/tuple.h +2 -2
  78. warp/native/vec.h +385 -18
  79. warp/native/volume.cpp +54 -54
  80. warp/native/volume.cu +1 -1
  81. warp/native/volume.h +2 -1
  82. warp/native/volume_builder.cu +30 -37
  83. warp/native/warp.cpp +150 -149
  84. warp/native/warp.cu +337 -193
  85. warp/native/warp.h +227 -226
  86. warp/optim/linear.py +736 -271
  87. warp/render/imgui_manager.py +289 -0
  88. warp/render/render_opengl.py +137 -57
  89. warp/render/render_usd.py +0 -1
  90. warp/sim/collide.py +1 -2
  91. warp/sim/graph_coloring.py +2 -2
  92. warp/sim/integrator_vbd.py +10 -2
  93. warp/sparse.py +559 -176
  94. warp/tape.py +2 -0
  95. warp/tests/aux_test_module_aot.py +7 -0
  96. warp/tests/cuda/test_async.py +3 -3
  97. warp/tests/cuda/test_conditional_captures.py +101 -0
  98. warp/tests/geometry/test_marching_cubes.py +233 -12
  99. warp/tests/sim/test_cloth.py +89 -6
  100. warp/tests/sim/test_coloring.py +82 -7
  101. warp/tests/test_array.py +56 -5
  102. warp/tests/test_assert.py +53 -0
  103. warp/tests/test_atomic_cas.py +127 -114
  104. warp/tests/test_codegen.py +3 -2
  105. warp/tests/test_context.py +8 -15
  106. warp/tests/test_enum.py +136 -0
  107. warp/tests/test_examples.py +2 -2
  108. warp/tests/test_fem.py +45 -2
  109. warp/tests/test_fixedarray.py +229 -0
  110. warp/tests/test_func.py +18 -15
  111. warp/tests/test_future_annotations.py +7 -5
  112. warp/tests/test_linear_solvers.py +30 -0
  113. warp/tests/test_map.py +1 -1
  114. warp/tests/test_mat.py +1540 -378
  115. warp/tests/test_mat_assign_copy.py +178 -0
  116. warp/tests/test_mat_constructors.py +574 -0
  117. warp/tests/test_module_aot.py +287 -0
  118. warp/tests/test_print.py +69 -0
  119. warp/tests/test_quat.py +162 -34
  120. warp/tests/test_quat_assign_copy.py +145 -0
  121. warp/tests/test_reload.py +2 -1
  122. warp/tests/test_sparse.py +103 -0
  123. warp/tests/test_spatial.py +140 -34
  124. warp/tests/test_spatial_assign_copy.py +160 -0
  125. warp/tests/test_static.py +48 -0
  126. warp/tests/test_struct.py +43 -3
  127. warp/tests/test_tape.py +38 -0
  128. warp/tests/test_types.py +0 -20
  129. warp/tests/test_vec.py +216 -441
  130. warp/tests/test_vec_assign_copy.py +143 -0
  131. warp/tests/test_vec_constructors.py +325 -0
  132. warp/tests/tile/test_tile.py +206 -152
  133. warp/tests/tile/test_tile_cholesky.py +605 -0
  134. warp/tests/tile/test_tile_load.py +169 -0
  135. warp/tests/tile/test_tile_mathdx.py +2 -558
  136. warp/tests/tile/test_tile_matmul.py +179 -0
  137. warp/tests/tile/test_tile_mlp.py +1 -1
  138. warp/tests/tile/test_tile_reduce.py +100 -11
  139. warp/tests/tile/test_tile_shared_memory.py +16 -16
  140. warp/tests/tile/test_tile_sort.py +59 -55
  141. warp/tests/unittest_suites.py +16 -0
  142. warp/tests/walkthrough_debug.py +1 -1
  143. warp/thirdparty/unittest_parallel.py +108 -9
  144. warp/types.py +554 -264
  145. warp/utils.py +68 -86
  146. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/METADATA +28 -65
  147. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/RECORD +150 -138
  148. warp/native/marching.cpp +0 -19
  149. warp/native/marching.cu +0 -514
  150. warp/native/marching.h +0 -19
  151. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/WHEEL +0 -0
  152. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/licenses/LICENSE.md +0 -0
  153. {warp_lang-1.8.0.dist-info → warp_lang-1.9.0.dist-info}/top_level.txt +0 -0
warp/tape.py CHANGED
@@ -251,6 +251,8 @@ class Tape:
251
251
  else:
252
252
  grad = None
253
253
  setattr(adj, name, grad)
254
+ elif isinstance(a._cls.vars[name].type, wp.codegen.Struct):
255
+ setattr(adj, name, self.get_adjoint(getattr(a, name)))
254
256
  else:
255
257
  setattr(adj, name, getattr(a, name))
256
258
 
@@ -0,0 +1,7 @@
1
+ import warp as wp
2
+
3
+
4
+ @wp.kernel
5
+ def add_kernel(a: wp.array(dtype=wp.int32), b: wp.array(dtype=wp.int32), res: wp.array(dtype=wp.int32)):
6
+ i = wp.tid()
7
+ res[i] = a[i] + b[i]
@@ -426,13 +426,13 @@ def copy_template(test, src_ctor, dst_ctor, src_device, dst_device, n, params: C
426
426
  if expected_error_type is not None:
427
427
  # disable error output from Warp if we expect an exception
428
428
  try:
429
- saved_error_output_enabled = wp.context.runtime.core.is_error_output_enabled()
430
- wp.context.runtime.core.set_error_output_enabled(False)
429
+ saved_error_output_enabled = wp.context.runtime.core.wp_is_error_output_enabled()
430
+ wp.context.runtime.core.wp_set_error_output_enabled(False)
431
431
  with test.assertRaisesRegex(expected_error_type, expected_error_regex):
432
432
  with Capturable(use_graph=params.use_graph, stream=stream):
433
433
  wp.copy(dst, src, stream=stream_arg)
434
434
  finally:
435
- wp.context.runtime.core.set_error_output_enabled(saved_error_output_enabled)
435
+ wp.context.runtime.core.wp_set_error_output_enabled(saved_error_output_enabled)
436
436
  wp.synchronize()
437
437
 
438
438
  # print(f"SUCCESSFUL ERROR PREDICTION: {expected_error_regex}")
@@ -979,6 +979,94 @@ def test_graph_debug_dot_print(test, device):
979
979
  os.remove(dot_file)
980
980
 
981
981
 
982
+ # ================================================================================================================
983
+ # test exceptions
984
+ # ================================================================================================================
985
+
986
+
987
+ # body graphs with allocations are not supported
988
+ def body_with_alloc():
989
+ wp.zeros(10)
990
+
991
+
992
+ @unittest.skipUnless(check_conditional_graph_support(), "Conditional graph nodes not supported")
993
+ def test_error_alloc_if(test, device):
994
+ with wp.ScopedDevice(device):
995
+ cond = wp.ones(1, dtype=wp.int32)
996
+ with test.assertRaisesRegex(
997
+ RuntimeError, r"Conditional body graph contains an unsupported operation \(memory allocation\)"
998
+ ):
999
+ with wp.ScopedCapture():
1000
+ wp.capture_if(condition=cond, on_true=body_with_alloc)
1001
+
1002
+
1003
+ @unittest.skipUnless(check_conditional_graph_support(), "Conditional graph nodes not supported")
1004
+ def test_error_alloc_else(test, device):
1005
+ with wp.ScopedDevice(device):
1006
+ cond = wp.ones(1, dtype=wp.int32)
1007
+ with test.assertRaisesRegex(
1008
+ RuntimeError, r"Conditional body graph contains an unsupported operation \(memory allocation\)"
1009
+ ):
1010
+ with wp.ScopedCapture():
1011
+ wp.capture_if(condition=cond, on_false=body_with_alloc)
1012
+
1013
+
1014
+ @unittest.skipUnless(check_conditional_graph_support(), "Conditional graph nodes not supported")
1015
+ def test_error_alloc_while(test, device):
1016
+ with wp.ScopedDevice(device):
1017
+ cond = wp.ones(1, dtype=wp.int32)
1018
+ with test.assertRaisesRegex(
1019
+ RuntimeError, r"Conditional body graph contains an unsupported operation \(memory allocation\)"
1020
+ ):
1021
+ with wp.ScopedCapture():
1022
+ wp.capture_while(condition=cond, while_body=body_with_alloc)
1023
+
1024
+
1025
+ @unittest.skipUnless(check_conditional_graph_support(), "Conditional graph nodes not supported")
1026
+ def test_error_alloc_if_subgraph(test, device):
1027
+ with wp.ScopedDevice(device):
1028
+ # capture body subgraph
1029
+ with wp.ScopedCapture() as body_capture:
1030
+ body_with_alloc()
1031
+
1032
+ cond = wp.ones(1, dtype=wp.int32)
1033
+ with test.assertRaisesRegex(
1034
+ RuntimeError, r"Child graph contains an unsupported operation \(memory allocation\)"
1035
+ ):
1036
+ with wp.ScopedCapture():
1037
+ wp.capture_if(condition=cond, on_true=body_capture.graph)
1038
+
1039
+
1040
+ @unittest.skipUnless(check_conditional_graph_support(), "Conditional graph nodes not supported")
1041
+ def test_error_alloc_else_subgraph(test, device):
1042
+ with wp.ScopedDevice(device):
1043
+ # capture body subgraph
1044
+ with wp.ScopedCapture() as body_capture:
1045
+ body_with_alloc()
1046
+
1047
+ cond = wp.ones(1, dtype=wp.int32)
1048
+ with test.assertRaisesRegex(
1049
+ RuntimeError, r"Child graph contains an unsupported operation \(memory allocation\)"
1050
+ ):
1051
+ with wp.ScopedCapture():
1052
+ wp.capture_if(condition=cond, on_false=body_capture.graph)
1053
+
1054
+
1055
+ @unittest.skipUnless(check_conditional_graph_support(), "Conditional graph nodes not supported")
1056
+ def test_error_alloc_while_subgraph(test, device):
1057
+ with wp.ScopedDevice(device):
1058
+ # capture body subgraph
1059
+ with wp.ScopedCapture() as body_capture:
1060
+ body_with_alloc()
1061
+
1062
+ cond = wp.ones(1, dtype=wp.int32)
1063
+ with test.assertRaisesRegex(
1064
+ RuntimeError, r"Child graph contains an unsupported operation \(memory allocation\)"
1065
+ ):
1066
+ with wp.ScopedCapture():
1067
+ wp.capture_while(condition=cond, while_body=body_capture.graph)
1068
+
1069
+
982
1070
  devices = get_test_devices()
983
1071
  cuda_devices = get_cuda_test_devices()
984
1072
 
@@ -1040,6 +1128,19 @@ add_function_test(
1040
1128
  TestConditionalCaptures, "test_graph_debug_dot_print", test_graph_debug_dot_print, devices=cuda_devices
1041
1129
  )
1042
1130
 
1131
+ add_function_test(TestConditionalCaptures, "test_error_alloc_if", test_error_alloc_if, devices=cuda_devices)
1132
+ add_function_test(TestConditionalCaptures, "test_error_alloc_else", test_error_alloc_else, devices=cuda_devices)
1133
+ add_function_test(TestConditionalCaptures, "test_error_alloc_while", test_error_alloc_while, devices=cuda_devices)
1134
+ add_function_test(
1135
+ TestConditionalCaptures, "test_error_alloc_if_subgraph", test_error_alloc_if_subgraph, devices=cuda_devices
1136
+ )
1137
+ add_function_test(
1138
+ TestConditionalCaptures, "test_error_alloc_else_subgraph", test_error_alloc_else_subgraph, devices=cuda_devices
1139
+ )
1140
+ add_function_test(
1141
+ TestConditionalCaptures, "test_error_alloc_while_subgraph", test_error_alloc_while_subgraph, devices=cuda_devices
1142
+ )
1143
+
1043
1144
 
1044
1145
  if __name__ == "__main__":
1045
1146
  wp.clear_kernel_cache()
@@ -22,7 +22,9 @@ from warp.tests.unittest_utils import *
22
22
 
23
23
 
24
24
  @wp.kernel
25
- def make_field(field: wp.array3d(dtype=float), center: wp.vec3, radius: float):
25
+ def make_field_sphere_sdf(field: wp.array3d(dtype=float), center: wp.vec3, radius: float):
26
+ """Make a sphere SDF for nodes on the integer domain with node coordinates 0,1,2,3,..."""
27
+
26
28
  i, j, k = wp.tid()
27
29
 
28
30
  p = wp.vec3(float(i), float(j), float(k))
@@ -32,31 +34,242 @@ def make_field(field: wp.array3d(dtype=float), center: wp.vec3, radius: float):
32
34
  field[i, j, k] = d
33
35
 
34
36
 
35
- def test_marching_cubes(test, device):
36
- dim = 64
37
- max_verts = 10**6
38
- max_tris = 10**6
37
+ @wp.kernel
38
+ def make_field_sphere_sdf_unit_domain(
39
+ field: wp.array3d(dtype=float), center: wp.vec3, radius: wp.array(dtype=wp.float32)
40
+ ):
41
+ """Makes a sphere SDF for nodes on the unit domain [-1, 1]^3."""
42
+ i, j, k = wp.tid()
39
43
 
40
- field = wp.zeros(shape=(dim, dim, dim), dtype=float, device=device)
44
+ nx, ny, nz = field.shape[0], field.shape[1], field.shape[2]
45
+
46
+ p = wp.vec3(
47
+ 2.0 * wp.float32(i) / (wp.float32(nx) - 1.0) - 1.0,
48
+ 2.0 * wp.float32(j) / (wp.float32(ny) - 1.0) - 1.0,
49
+ 2.0 * wp.float32(k) / (wp.float32(nz) - 1.0) - 1.0,
50
+ )
51
+
52
+ d = wp.length(p - center) - radius[0]
53
+
54
+ field[i, j, k] = d
55
+
56
+
57
+ @wp.kernel
58
+ def compute_surface_area(
59
+ verts: wp.array(dtype=wp.vec3), faces: wp.array(dtype=wp.int32), out_area: wp.array(dtype=wp.float32)
60
+ ):
61
+ tid = wp.tid()
62
+ vi = faces[3 * tid + 0]
63
+ vj = faces[3 * tid + 1]
64
+ vk = faces[3 * tid + 2]
65
+
66
+ p0 = verts[vi]
67
+ p1 = verts[vj]
68
+ p2 = verts[vk]
69
+
70
+ # Heron's formula for triangle area
71
+ a = wp.length(p1 - p0)
72
+ b = wp.length(p2 - p0)
73
+ c = wp.length(p2 - p1)
74
+ s = (a + b + c) / 2.0
75
+ area = wp.sqrt(s * (s - a) * (s - b) * (s - c))
41
76
 
42
- iso = wp.MarchingCubes(nx=dim, ny=dim, nz=dim, max_verts=max_verts, max_tris=max_tris, device=device)
77
+ wp.atomic_add(out_area, 0, area)
43
78
 
44
- radius = dim / 4.0
45
79
 
46
- wp.launch(make_field, dim=field.shape, inputs=[field, wp.vec3(dim / 2, dim / 2, dim / 2), radius], device=device)
80
+ def validate_marching_cubes_output(test, verts_np, faces_np, check_nonempty=True):
81
+ # check that the face array seems valid
82
+ if check_nonempty:
83
+ test.assertGreater(faces_np.shape[0], 0) # at least one face
84
+ test.assertEqual(faces_np.shape[1], 3) # all faces triangular
85
+ test.assertTrue((faces_np >= 0).all()) # all face inds nonnegative
86
+ test.assertTrue((faces_np < verts_np.shape[0]).all()) # all face inds are in-bounds on the vertex array
87
+ test.assertTrue((faces_np[:, 0] != faces_np[:, 1]).all()) # all faces have unique vertices
88
+ test.assertTrue((faces_np[:, 0] != faces_np[:, 2]).all()) # all faces have unique vertices
89
+ test.assertTrue((faces_np[:, 1] != faces_np[:, 2]).all()) # all faces have unique vertices
90
+ test.assertTrue(
91
+ (np.unique(faces_np.flatten()) == np.arange(verts_np.shape[0])).all()
92
+ ) # all vertices are used in at least one face
93
+
94
+ # check that the vertex array seems valid
95
+ if check_nonempty:
96
+ test.assertGreater(verts_np.shape[0], 0) # at least one vertex
97
+ test.assertEqual(verts_np.shape[1], 3) # all vertices are 3D
98
+ test.assertTrue(np.isfinite(verts_np).all()) # all vertices are finite
99
+
100
+
101
+ def test_marching_cubes(test, device):
102
+ """Basic test of typical usage."""
103
+ node_dim = 64
104
+ cell_dim = node_dim - 1
105
+ field = wp.zeros(shape=(node_dim, node_dim, node_dim), dtype=float, device=device)
106
+ bounds_low = (0.0, 0.0, 0.0)
107
+ bounds_high = (float(cell_dim), float(cell_dim), float(cell_dim))
108
+
109
+ iso = wp.MarchingCubes(
110
+ nx=node_dim,
111
+ ny=node_dim,
112
+ nz=node_dim,
113
+ device=device,
114
+ domain_bounds_lower_corner=bounds_low,
115
+ domain_bounds_upper_corner=bounds_high,
116
+ )
117
+
118
+ radius = node_dim / 4.0
119
+
120
+ wp.launch(
121
+ make_field_sphere_sdf,
122
+ dim=field.shape,
123
+ inputs=[field, wp.vec3(node_dim / 2, node_dim / 2, node_dim / 2), radius],
124
+ device=device,
125
+ )
47
126
 
48
127
  iso.surface(field=field, threshold=0.0)
128
+ verts_np = iso.verts.numpy()
129
+ faces_np = iso.indices.numpy().reshape(-1, 3)
130
+ test.assertEqual(
131
+ iso.indices.dtype, wp.int32
132
+ ) # make sure we are following Warp convention of using a flat array of indices
133
+ validate_marching_cubes_output(test, verts_np, faces_np)
49
134
 
50
135
  # check that all returned vertices lie on the surface of the sphere
51
- length = np.linalg.norm(iso.verts.numpy() - np.array([dim / 2, dim / 2, dim / 2]), axis=1)
136
+ length = np.linalg.norm(verts_np - np.array([node_dim / 2, node_dim / 2, node_dim / 2]), axis=1)
52
137
  error = np.abs(length - radius)
138
+ test.assertTrue(np.max(error) < 1.0)
139
+
140
+ iso.resize(nx=node_dim * 2, ny=node_dim * 2, nz=node_dim * 2) # smoke test for deprecated function
141
+
142
+
143
+ def test_marching_cubes_functional(test, device):
144
+ """Ensure the single-function interface works as expected."""
145
+ node_dim = 64
146
+ cell_dim = node_dim - 1
147
+ field = wp.zeros(shape=(node_dim, node_dim, node_dim), dtype=float, device=device)
148
+ bounds_low = (0.0, 0.0, 0.0)
149
+ bounds_high = (float(cell_dim), float(cell_dim), float(cell_dim))
150
+
151
+ radius = node_dim / 4.0
152
+ wp.launch(
153
+ make_field_sphere_sdf,
154
+ dim=field.shape,
155
+ inputs=[field, wp.vec3(node_dim / 2, node_dim / 2, node_dim / 2), radius],
156
+ device=device,
157
+ )
158
+
159
+ # call via the functional interface
160
+ verts, faces = wp.MarchingCubes.extract_surface_marching_cubes(
161
+ field, threshold=0.0, domain_bounds_lower_corner=bounds_low, domain_bounds_upper_corner=bounds_high
162
+ )
163
+
164
+ verts_np = verts.numpy()
165
+ faces_np = faces.numpy().reshape(-1, 3)
166
+ validate_marching_cubes_output(test, verts_np, faces_np)
53
167
 
168
+ # check that all returned vertices lie on the surface of the sphere
169
+ length = np.linalg.norm(verts_np - np.array([node_dim / 2, node_dim / 2, node_dim / 2]), axis=1)
170
+ error = np.abs(length - radius)
54
171
  test.assertTrue(np.max(error) < 1.0)
55
172
 
56
- iso.resize(nx=dim * 2, ny=dim * 2, nz=dim * 2, max_verts=max_verts, max_tris=max_tris)
173
+
174
+ def test_marching_cubes_nonuniform(test, device):
175
+ """Test the logic for when the dimensions of the grid are not uniform."""
176
+
177
+ dimX = 64
178
+ dimY = 48
179
+ dimZ = 72
180
+ field = wp.zeros(shape=(dimX, dimY, dimZ), dtype=float, device=device)
181
+
182
+ bounds_low = wp.vec3(0.0, 0.0, 0.0)
183
+ bounds_high = wp.vec3(float(dimX), float(dimY), float(dimZ))
184
+
185
+ iso = wp.MarchingCubes(
186
+ nx=dimX,
187
+ ny=dimY,
188
+ nz=dimZ,
189
+ device=device,
190
+ domain_bounds_lower_corner=bounds_low,
191
+ domain_bounds_upper_corner=bounds_high,
192
+ )
193
+
194
+ radius = dimX / 4.0
195
+ wp.launch(
196
+ make_field_sphere_sdf,
197
+ dim=field.shape,
198
+ inputs=[field, wp.vec3(dimX / 2, dimY / 2, dimZ / 2), radius],
199
+ device=device,
200
+ )
201
+
202
+ iso.surface(field=field, threshold=0.0)
203
+ verts_np = iso.verts.numpy()
204
+ faces_np = iso.indices.numpy().reshape(-1, 3)
205
+ validate_marching_cubes_output(test, verts_np, faces_np)
206
+
207
+
208
+ def test_marching_cubes_empty_output(test, device):
209
+ """Make sure we handle the empty-output case correctly."""
210
+
211
+ dim = 64
212
+ field = wp.zeros(shape=(dim, dim, dim), dtype=float, device=device)
213
+
214
+ iso = wp.MarchingCubes(nx=dim, ny=dim, nz=dim, device=device)
215
+
216
+ wp.launch(make_field_sphere_sdf, dim=field.shape, inputs=[field, wp.vec3(0.5, 0.5, 0.5), 0.25], device=device)
217
+
218
+ iso.surface(field=field, threshold=1000.0) # set threshold to a large value so that no vertices are generated
219
+ verts_np = iso.verts.numpy()
220
+ faces_np = iso.indices.numpy().reshape(-1, 3)
221
+ validate_marching_cubes_output(test, verts_np, faces_np, check_nonempty=False)
222
+
223
+ test.assertEqual(faces_np.shape[0], 0) # no faces
224
+ test.assertEqual(verts_np.shape[0], 0) # no vertices
225
+
226
+
227
+ def test_marching_cubes_differentiable(test, device):
228
+ """Check that marching cubes has reasonable gradients.
229
+
230
+ This test constructs an SDF of sphere, extracts a surface, computes its
231
+ surface area, and then differentiates the surface area with respect to
232
+ the sphere's radius.
233
+ """
234
+ node_dim = 64
235
+ cell_dim = node_dim - 1
236
+ bounds_low = wp.vec3(-1.0, -1.0, -1.0)
237
+ bounds_high = wp.vec3(1.0, 1.0, 1.0)
238
+
239
+ radius = 0.5
240
+ radius_wp = wp.full((1,), value=0.5, dtype=wp.float32, device=device, requires_grad=True)
241
+
242
+ with wp.Tape() as tape:
243
+ field = wp.zeros(shape=(node_dim, node_dim, node_dim), dtype=float, device=device, requires_grad=True)
244
+ wp.launch(
245
+ make_field_sphere_sdf_unit_domain,
246
+ dim=field.shape,
247
+ inputs=[field, wp.vec3(0.0, 0.0, 0.0), radius_wp],
248
+ device=device,
249
+ )
250
+
251
+ # call via the functional interface
252
+ verts, faces = wp.MarchingCubes.extract_surface_marching_cubes(
253
+ field, threshold=0.0, domain_bounds_lower_corner=bounds_low, domain_bounds_upper_corner=bounds_high
254
+ )
255
+
256
+ # compute surface area
257
+ area = wp.zeros(shape=(1,), dtype=float, device=device, requires_grad=True)
258
+ wp.launch(compute_surface_area, dim=faces.shape[0] // 3, inputs=[verts, faces, area], device=device)
259
+
260
+ # confirm surface area is correct vs. the analytical ground truth
261
+ area_np = area.numpy()[0]
262
+ test.assertTrue(np.abs(area_np - 4.0 * np.pi * radius * radius) < 1e-2)
263
+
264
+ # compute the gradient of the surface area with respect to the radius
265
+ tape.backward(area)
266
+
267
+ # confirm the gradient is correct vs. the analytical ground truth
268
+ grad_np = radius_wp.grad.numpy()[0]
269
+ test.assertTrue(np.abs(grad_np - 8.0 * np.pi * radius) < 1e-2)
57
270
 
58
271
 
59
- devices = get_selected_cuda_test_devices()
272
+ devices = get_test_devices()
60
273
 
61
274
 
62
275
  class TestMarchingCubes(unittest.TestCase):
@@ -67,6 +280,14 @@ class TestMarchingCubes(unittest.TestCase):
67
280
 
68
281
 
69
282
  add_function_test(TestMarchingCubes, "test_marching_cubes", test_marching_cubes, devices=devices)
283
+ add_function_test(TestMarchingCubes, "test_marching_cubes_functional", test_marching_cubes_functional, devices=devices)
284
+ add_function_test(TestMarchingCubes, "test_marching_cubes_nonuniform", test_marching_cubes_nonuniform, devices=devices)
285
+ add_function_test(
286
+ TestMarchingCubes, "test_marching_cubes_empty_output", test_marching_cubes_empty_output, devices=devices
287
+ )
288
+ add_function_test(
289
+ TestMarchingCubes, "test_marching_cubes_differentiable", test_marching_cubes_differentiable, devices=devices
290
+ )
70
291
 
71
292
 
72
293
  if __name__ == "__main__":
@@ -25,6 +25,7 @@ import warp.sim.integrator_euler
25
25
  import warp.sim.integrator_vbd
26
26
  import warp.sim.integrator_xpbd
27
27
  import warp.sim.particles
28
+ import warp.sim.render
28
29
  from warp.sim.model import PARTICLE_FLAG_ACTIVE
29
30
  from warp.tests.unittest_utils import *
30
31
 
@@ -299,14 +300,16 @@ CLOTH_FACES = [
299
300
 
300
301
  # fmt: on
301
302
  class ClothSim:
302
- def __init__(self, device, solver, use_cuda_graph=False):
303
+ def __init__(self, device, solver, use_cuda_graph=False, do_rendering=False):
303
304
  self.frame_dt = 1 / 60
304
305
  self.num_test_frames = 50
305
306
  self.iterations = 5
307
+ self.do_rendering = do_rendering
306
308
  self.device = device
307
309
  self.use_cuda_graph = self.device.is_cuda and use_cuda_graph
308
310
  self.builder = wp.sim.ModelBuilder()
309
311
  self.solver = solver
312
+ self.renderer_scale_factor = 1.0
310
313
 
311
314
  if solver != "semi_implicit":
312
315
  self.num_substeps = 10
@@ -434,7 +437,7 @@ class ClothSim:
434
437
 
435
438
  self.finalize()
436
439
 
437
- def set_collision_experiment(self):
440
+ def set_self_collision_experiment(self):
438
441
  elasticity_ke = 1e4
439
442
  elasticity_kd = 1e-6
440
443
 
@@ -487,6 +490,44 @@ class ClothSim:
487
490
  self.model.gravity = wp.vec3(0.0, -1000.0, 0)
488
491
  self.num_test_frames = 30
489
492
 
493
+ def set_body_collision_experiment(self, handle_self_contact=False):
494
+ self.renderer_scale_factor = 1.0
495
+ elasticity_ke = 1e4
496
+ elasticity_kd = 1e-6
497
+
498
+ vs1 = [wp.vec3(v) for v in [[0, 0, 0], [1, 0, 0], [1, 0, 1], [0, 0, 1]]]
499
+ fs1 = [0, 1, 2, 0, 2, 3]
500
+
501
+ self.builder.add_cloth_mesh(
502
+ pos=wp.vec3(0.0, 0.0, 0.0),
503
+ rot=wp.quat_from_axis_angle(wp.vec3(1.0, 0.0, 0.0), 0.0),
504
+ scale=1.0,
505
+ vertices=vs1,
506
+ indices=fs1,
507
+ vel=wp.vec3(0.0, 0.0, 0.0),
508
+ density=0.02,
509
+ tri_ke=elasticity_ke,
510
+ tri_ka=elasticity_ke,
511
+ tri_kd=elasticity_kd,
512
+ add_springs=self.solver == "xpbd",
513
+ spring_ke=1.0e3,
514
+ spring_kd=0.0,
515
+ particle_radius=0.1,
516
+ )
517
+
518
+ self.builder.add_shape_box(-1, pos=wp.vec3(0, -3.3, 0), hx=3, hy=3, hz=3)
519
+
520
+ self.fixed_particles = []
521
+
522
+ self.finalize(handle_self_contact=handle_self_contact, ground=False)
523
+ self.model.soft_contact_radius = 0.1
524
+ self.model.soft_contact_margin = 0.1
525
+ self.model.soft_contact_ke = 1e4
526
+ self.model.soft_contact_kd = 1e-3
527
+ self.model.soft_contact_mu = 0.2
528
+ self.model.gravity = wp.vec3(0.0, -1000.0, 0)
529
+ self.num_test_frames = 30
530
+
490
531
  def set_up_non_zero_rest_angle_bending_experiment(self):
491
532
  # fmt: off
492
533
  vs =[
@@ -629,6 +670,9 @@ class ClothSim:
629
670
  wp.load_module(warp.sim.integrator_euler, device=self.device)
630
671
  wp.load_module(warp.sim.particles, device=self.device)
631
672
  wp.load_module(warp.sim.integrator, device=self.device)
673
+ collide_module = importlib.import_module("warp.sim.collide")
674
+ wp.set_module_options({"block_dim": 256}, collide_module)
675
+ wp.load_module(collide_module, device=self.device)
632
676
  wp.load_module(device=self.device)
633
677
  with wp.ScopedCapture(device=self.device, force_module_load=False) as capture:
634
678
  self.simulate()
@@ -637,17 +681,30 @@ class ClothSim:
637
681
  def simulate(self):
638
682
  for _step in range(self.num_substeps):
639
683
  self.state0.clear_forces()
684
+ wp.sim.collide(self.model, self.state0)
640
685
 
641
686
  self.integrator.simulate(self.model, self.state0, self.state1, self.dt, None)
642
687
  (self.state0, self.state1) = (self.state1, self.state0)
643
688
 
644
689
  def run(self):
645
690
  self.sim_time = 0.0
691
+
692
+ if self.do_rendering:
693
+ self.renderer = wp.sim.render.SimRendererOpenGL(self.model, "cloth_sim", scaling=self.renderer_scale_factor)
694
+ else:
695
+ self.renderer = None
696
+
646
697
  for _frame in range(self.num_test_frames):
647
698
  if self.graph:
648
699
  wp.capture_launch(self.graph)
649
700
  else:
650
701
  self.simulate()
702
+
703
+ if self.renderer is not None:
704
+ self.renderer.begin_frame()
705
+ self.renderer.render(self.state0)
706
+ self.renderer.end_frame()
707
+
651
708
  self.sim_time = self.sim_time + self.frame_dt
652
709
 
653
710
  def set_points_fixed(self, model, fixed_particles):
@@ -702,16 +759,41 @@ def test_cloth_bending_non_zero_rest_angle_bending(test, device, solver):
702
759
  test.assertTrue((example.init_pos != final_pos).any())
703
760
 
704
761
 
705
- def test_cloth_collision(test, device, solver):
762
+ def test_cloth_body_collision(test, device, solver):
763
+ example = ClothSim(device, solver, use_cuda_graph=True)
764
+ example.set_body_collision_experiment(handle_self_contact=False)
765
+
766
+ example.run()
767
+
768
+ # examine that the velocity has died out
769
+ final_vel = example.state0.particle_qd.numpy()
770
+ final_pos = example.state0.particle_q.numpy()
771
+ test.assertTrue((np.linalg.norm(final_vel, axis=1) < 1.0).all())
772
+ # examine that the simulation has moved
773
+ test.assertTrue((example.init_pos[:, 1] != final_pos[:, 1]).any())
774
+
775
+ # run again with handle_self_contact=True
776
+ example = ClothSim(device, solver, use_cuda_graph=True)
777
+ example.set_body_collision_experiment(handle_self_contact=True)
778
+ example.run()
779
+ # examine that the velocity has died out
780
+ final_vel = example.state0.particle_qd.numpy()
781
+ final_pos = example.state0.particle_q.numpy()
782
+ test.assertTrue((np.linalg.norm(final_vel, axis=1) < 1.0).all())
783
+ # examine that the simulation has moved
784
+ test.assertTrue((example.init_pos[:, 1] != final_pos[:, 1]).any())
785
+
786
+
787
+ def test_cloth_self_collision(test, device, solver):
706
788
  example = ClothSim(device, solver, use_cuda_graph=True)
707
- example.set_collision_experiment()
789
+ example.set_self_collision_experiment()
708
790
 
709
791
  example.run()
710
792
 
711
793
  # examine that the velocity has died out
712
794
  final_vel = example.state0.particle_qd.numpy()
713
795
  final_pos = example.state0.particle_q.numpy()
714
- test.assertTrue((np.linalg.norm(final_vel, axis=0) < 1.0).all())
796
+ test.assertTrue((np.linalg.norm(final_vel, axis=1) < 1.0).all())
715
797
  # examine that the simulation has moved
716
798
  test.assertTrue((example.init_pos != final_pos).any())
717
799
 
@@ -763,7 +845,8 @@ tests_to_run = {
763
845
  test_cloth_free_fall,
764
846
  test_cloth_sagging,
765
847
  test_cloth_bending,
766
- test_cloth_collision,
848
+ test_cloth_self_collision,
849
+ test_cloth_body_collision,
767
850
  test_cloth_bending_non_zero_rest_angle_bending,
768
851
  ],
769
852
  }