warp-lang 1.8.1__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 (134) 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 +47 -67
  7. warp/builtins.py +955 -137
  8. warp/codegen.py +312 -206
  9. warp/config.py +1 -1
  10. warp/context.py +1249 -784
  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/fabric.py +1 -1
  18. warp/fem/cache.py +27 -19
  19. warp/fem/domain.py +2 -2
  20. warp/fem/field/nodal_field.py +2 -2
  21. warp/fem/field/virtual.py +264 -166
  22. warp/fem/geometry/geometry.py +5 -5
  23. warp/fem/integrate.py +129 -51
  24. warp/fem/space/restriction.py +4 -0
  25. warp/fem/space/shape/tet_shape_function.py +3 -10
  26. warp/jax_experimental/custom_call.py +1 -1
  27. warp/jax_experimental/ffi.py +2 -1
  28. warp/marching_cubes.py +708 -0
  29. warp/native/array.h +99 -4
  30. warp/native/builtin.h +82 -5
  31. warp/native/bvh.cpp +64 -28
  32. warp/native/bvh.cu +58 -58
  33. warp/native/bvh.h +2 -2
  34. warp/native/clang/clang.cpp +7 -7
  35. warp/native/coloring.cpp +8 -2
  36. warp/native/crt.cpp +2 -2
  37. warp/native/crt.h +3 -5
  38. warp/native/cuda_util.cpp +41 -10
  39. warp/native/cuda_util.h +10 -4
  40. warp/native/exports.h +1842 -1908
  41. warp/native/fabric.h +2 -1
  42. warp/native/hashgrid.cpp +37 -37
  43. warp/native/hashgrid.cu +2 -2
  44. warp/native/initializer_array.h +1 -1
  45. warp/native/intersect.h +2 -2
  46. warp/native/mat.h +1910 -116
  47. warp/native/mathdx.cpp +43 -43
  48. warp/native/mesh.cpp +24 -24
  49. warp/native/mesh.cu +26 -26
  50. warp/native/mesh.h +4 -2
  51. warp/native/nanovdb/GridHandle.h +179 -12
  52. warp/native/nanovdb/HostBuffer.h +8 -7
  53. warp/native/nanovdb/NanoVDB.h +517 -895
  54. warp/native/nanovdb/NodeManager.h +323 -0
  55. warp/native/nanovdb/PNanoVDB.h +2 -2
  56. warp/native/quat.h +331 -14
  57. warp/native/range.h +7 -1
  58. warp/native/reduce.cpp +10 -10
  59. warp/native/reduce.cu +13 -14
  60. warp/native/runlength_encode.cpp +2 -2
  61. warp/native/runlength_encode.cu +5 -5
  62. warp/native/scan.cpp +3 -3
  63. warp/native/scan.cu +4 -4
  64. warp/native/sort.cpp +10 -10
  65. warp/native/sort.cu +22 -22
  66. warp/native/sparse.cpp +8 -8
  67. warp/native/sparse.cu +13 -13
  68. warp/native/spatial.h +366 -17
  69. warp/native/temp_buffer.h +2 -2
  70. warp/native/tile.h +283 -69
  71. warp/native/vec.h +381 -14
  72. warp/native/volume.cpp +54 -54
  73. warp/native/volume.cu +1 -1
  74. warp/native/volume.h +2 -1
  75. warp/native/volume_builder.cu +30 -37
  76. warp/native/warp.cpp +150 -149
  77. warp/native/warp.cu +323 -192
  78. warp/native/warp.h +227 -226
  79. warp/optim/linear.py +736 -271
  80. warp/render/imgui_manager.py +289 -0
  81. warp/render/render_opengl.py +85 -6
  82. warp/sim/graph_coloring.py +2 -2
  83. warp/sparse.py +558 -175
  84. warp/tests/aux_test_module_aot.py +7 -0
  85. warp/tests/cuda/test_async.py +3 -3
  86. warp/tests/cuda/test_conditional_captures.py +101 -0
  87. warp/tests/geometry/test_marching_cubes.py +233 -12
  88. warp/tests/sim/test_coloring.py +6 -6
  89. warp/tests/test_array.py +56 -5
  90. warp/tests/test_codegen.py +3 -2
  91. warp/tests/test_context.py +8 -15
  92. warp/tests/test_enum.py +136 -0
  93. warp/tests/test_examples.py +2 -2
  94. warp/tests/test_fem.py +45 -2
  95. warp/tests/test_fixedarray.py +229 -0
  96. warp/tests/test_func.py +18 -15
  97. warp/tests/test_future_annotations.py +7 -5
  98. warp/tests/test_linear_solvers.py +30 -0
  99. warp/tests/test_map.py +1 -1
  100. warp/tests/test_mat.py +1518 -378
  101. warp/tests/test_mat_assign_copy.py +178 -0
  102. warp/tests/test_mat_constructors.py +574 -0
  103. warp/tests/test_module_aot.py +287 -0
  104. warp/tests/test_print.py +69 -0
  105. warp/tests/test_quat.py +140 -34
  106. warp/tests/test_quat_assign_copy.py +145 -0
  107. warp/tests/test_reload.py +2 -1
  108. warp/tests/test_sparse.py +71 -0
  109. warp/tests/test_spatial.py +140 -34
  110. warp/tests/test_spatial_assign_copy.py +160 -0
  111. warp/tests/test_struct.py +43 -3
  112. warp/tests/test_types.py +0 -20
  113. warp/tests/test_vec.py +179 -34
  114. warp/tests/test_vec_assign_copy.py +143 -0
  115. warp/tests/tile/test_tile.py +184 -18
  116. warp/tests/tile/test_tile_cholesky.py +605 -0
  117. warp/tests/tile/test_tile_load.py +169 -0
  118. warp/tests/tile/test_tile_mathdx.py +2 -558
  119. warp/tests/tile/test_tile_matmul.py +1 -1
  120. warp/tests/tile/test_tile_mlp.py +1 -1
  121. warp/tests/tile/test_tile_shared_memory.py +5 -5
  122. warp/tests/unittest_suites.py +6 -0
  123. warp/tests/walkthrough_debug.py +1 -1
  124. warp/thirdparty/unittest_parallel.py +108 -9
  125. warp/types.py +554 -264
  126. warp/utils.py +68 -86
  127. {warp_lang-1.8.1.dist-info → warp_lang-1.9.0.dist-info}/METADATA +28 -65
  128. {warp_lang-1.8.1.dist-info → warp_lang-1.9.0.dist-info}/RECORD +131 -121
  129. warp/native/marching.cpp +0 -19
  130. warp/native/marching.cu +0 -514
  131. warp/native/marching.h +0 -19
  132. {warp_lang-1.8.1.dist-info → warp_lang-1.9.0.dist-info}/WHEEL +0 -0
  133. {warp_lang-1.8.1.dist-info → warp_lang-1.9.0.dist-info}/licenses/LICENSE.md +0 -0
  134. {warp_lang-1.8.1.dist-info → warp_lang-1.9.0.dist-info}/top_level.txt +0 -0
@@ -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__":
@@ -150,7 +150,7 @@ def test_coloring_trimesh(test, device):
150
150
  edge_indices_cpu = wp.array(model.edge_indices.numpy()[:, 2:], dtype=int, device="cpu")
151
151
 
152
152
  # coloring without bending
153
- num_colors_greedy = wp.context.runtime.core.graph_coloring(
153
+ num_colors_greedy = wp.context.runtime.core.wp_graph_coloring(
154
154
  model.particle_count,
155
155
  edge_indices_cpu.__ctype__(),
156
156
  ColoringAlgorithm.GREEDY.value,
@@ -163,7 +163,7 @@ def test_coloring_trimesh(test, device):
163
163
  device="cpu",
164
164
  )
165
165
 
166
- num_colors_mcs = wp.context.runtime.core.graph_coloring(
166
+ num_colors_mcs = wp.context.runtime.core.wp_graph_coloring(
167
167
  model.particle_count,
168
168
  edge_indices_cpu.__ctype__(),
169
169
  ColoringAlgorithm.MCS.value,
@@ -178,13 +178,13 @@ def test_coloring_trimesh(test, device):
178
178
 
179
179
  # coloring with bending
180
180
  edge_indices_cpu_with_bending = construct_trimesh_graph_edges(model.edge_indices, True)
181
- num_colors_greedy = wp.context.runtime.core.graph_coloring(
181
+ num_colors_greedy = wp.context.runtime.core.wp_graph_coloring(
182
182
  model.particle_count,
183
183
  edge_indices_cpu_with_bending.__ctype__(),
184
184
  ColoringAlgorithm.GREEDY.value,
185
185
  particle_colors.__ctype__(),
186
186
  )
187
- wp.context.runtime.core.balance_coloring(
187
+ wp.context.runtime.core.wp_balance_coloring(
188
188
  model.particle_count,
189
189
  edge_indices_cpu_with_bending.__ctype__(),
190
190
  num_colors_greedy,
@@ -198,13 +198,13 @@ def test_coloring_trimesh(test, device):
198
198
  device="cpu",
199
199
  )
200
200
 
201
- num_colors_mcs = wp.context.runtime.core.graph_coloring(
201
+ num_colors_mcs = wp.context.runtime.core.wp_graph_coloring(
202
202
  model.particle_count,
203
203
  edge_indices_cpu_with_bending.__ctype__(),
204
204
  ColoringAlgorithm.MCS.value,
205
205
  particle_colors.__ctype__(),
206
206
  )
207
- max_min_ratio = wp.context.runtime.core.balance_coloring(
207
+ max_min_ratio = wp.context.runtime.core.wp_balance_coloring(
208
208
  model.particle_count,
209
209
  edge_indices_cpu_with_bending.__ctype__(),
210
210
  num_colors_mcs,
warp/tests/test_array.py CHANGED
@@ -2902,10 +2902,8 @@ def test_direct_from_numpy(test, device):
2902
2902
 
2903
2903
 
2904
2904
  @wp.kernel
2905
- def kernel_array_from_ptr(
2906
- ptr: wp.uint64,
2907
- ):
2908
- arr = wp.array(ptr=ptr, shape=(2, 3), dtype=wp.float32)
2905
+ def kernel_array_from_ptr(arr_orig: wp.array2d(dtype=wp.float32)):
2906
+ arr = wp.array(ptr=arr_orig.ptr, shape=(2, 3), dtype=wp.float32)
2909
2907
  arr[0, 0] = 1.0
2910
2908
  arr[0, 1] = 2.0
2911
2909
  arr[0, 2] = 3.0
@@ -2913,7 +2911,56 @@ def kernel_array_from_ptr(
2913
2911
 
2914
2912
  def test_kernel_array_from_ptr(test, device):
2915
2913
  arr = wp.zeros(shape=(2, 3), dtype=wp.float32, device=device)
2916
- wp.launch(kernel_array_from_ptr, dim=(1,), inputs=(arr.ptr,), device=device)
2914
+ wp.launch(kernel_array_from_ptr, dim=(1,), inputs=(arr,), device=device)
2915
+ assert_np_equal(arr.numpy(), np.array(((1.0, 2.0, 3.0), (0.0, 0.0, 0.0))))
2916
+
2917
+
2918
+ @wp.struct
2919
+ class MyStruct:
2920
+ a: wp.float32
2921
+ b: wp.float32
2922
+ c: wp.float32
2923
+
2924
+
2925
+ @wp.kernel
2926
+ def kernel_array_from_ptr_struct(arr_orig: wp.array(dtype=MyStruct)):
2927
+ arr = wp.array(ptr=arr_orig.ptr, shape=(2,), dtype=MyStruct)
2928
+ arr[0].a = 1.0
2929
+ arr[0].b = 2.0
2930
+ arr[0].c = 3.0
2931
+ arr[1].a = 4.0
2932
+ arr[1].b = 5.0
2933
+ arr[1].c = 6.0
2934
+
2935
+
2936
+ def test_kernel_array_from_ptr_struct(test, device):
2937
+ arr = wp.zeros(shape=(2,), dtype=MyStruct, device=device)
2938
+ wp.launch(kernel_array_from_ptr_struct, dim=(1,), inputs=(arr,), device=device)
2939
+ arr_np = arr.numpy()
2940
+ expected = np.zeros_like(arr_np)
2941
+ expected[0] = (1.0, 2.0, 3.0)
2942
+ expected[1] = (4.0, 5.0, 6.0)
2943
+ assert_np_equal(arr_np, expected)
2944
+
2945
+
2946
+ @wp.kernel
2947
+ def kernel_array_from_ptr_variable_shape(
2948
+ ptr: wp.uint64,
2949
+ shape_x: int,
2950
+ shape_y: int,
2951
+ ):
2952
+ arr = wp.array(ptr=ptr, shape=(shape_x, shape_y), dtype=wp.float32)
2953
+ arr[0, 0] = 1.0
2954
+ arr[0, 1] = 2.0
2955
+ if shape_y > 2:
2956
+ arr[0, 2] = 3.0
2957
+
2958
+
2959
+ def test_kernel_array_from_ptr_variable_shape(test, device):
2960
+ arr = wp.zeros(shape=(2, 3), dtype=wp.float32, device=device)
2961
+ wp.launch(kernel_array_from_ptr_variable_shape, dim=(1,), inputs=(arr.ptr, 2, 2), device=device)
2962
+ assert_np_equal(arr.numpy(), np.array(((1.0, 2.0, 0.0), (0.0, 0.0, 0.0))))
2963
+ wp.launch(kernel_array_from_ptr_variable_shape, dim=(1,), inputs=(arr.ptr, 2, 3), device=device)
2917
2964
  assert_np_equal(arr.numpy(), np.array(((1.0, 2.0, 3.0), (0.0, 0.0, 0.0))))
2918
2965
 
2919
2966
 
@@ -3185,6 +3232,10 @@ add_function_test(TestArray, "test_array_inplace_diff_ops", test_array_inplace_d
3185
3232
  add_function_test(TestArray, "test_array_inplace_non_diff_ops", test_array_inplace_non_diff_ops, devices=devices)
3186
3233
  add_function_test(TestArray, "test_direct_from_numpy", test_direct_from_numpy, devices=["cpu"])
3187
3234
  add_function_test(TestArray, "test_kernel_array_from_ptr", test_kernel_array_from_ptr, devices=devices)
3235
+ add_function_test(TestArray, "test_kernel_array_from_ptr_struct", test_kernel_array_from_ptr_struct, devices=devices)
3236
+ add_function_test(
3237
+ TestArray, "test_kernel_array_from_ptr_variable_shape", test_kernel_array_from_ptr_variable_shape, devices=devices
3238
+ )
3188
3239
 
3189
3240
  add_function_test(TestArray, "test_array_from_int32_domain", test_array_from_int32_domain, devices=devices)
3190
3241
  add_function_test(TestArray, "test_array_from_int64_domain", test_array_from_int64_domain, devices=devices)
@@ -756,6 +756,7 @@ def test_multiple_return_values(test, device):
756
756
  test_multiple_return_values_quat_to_axis_angle_kernel,
757
757
  dim=1,
758
758
  inputs=(q, expected_axis, expected_angle),
759
+ device=device,
759
760
  )
760
761
 
761
762
  # fmt: off
@@ -791,9 +792,9 @@ def test_multiple_return_values(test, device):
791
792
 
792
793
  test.assertAlmostEqual(V[0][0], expected_V[0][0], places=5)
793
794
  test.assertAlmostEqual(V[0][1], expected_V[0][1], places=5)
794
- test.assertAlmostEqual(V[0][2], expected_V[0][2], places=5)
795
+ test.assertAlmostEqual(V[0][2], expected_V[0][2], places=4) # precision issue on ARM64 (GH-905)
795
796
  test.assertAlmostEqual(V[1][0], expected_V[1][0], places=5)
796
- test.assertAlmostEqual(V[1][1], expected_V[1][1], places=5)
797
+ test.assertAlmostEqual(V[1][1], expected_V[1][1], places=4) # precision issue on ARM64 (GH-905)
797
798
  test.assertAlmostEqual(V[1][2], expected_V[1][2], places=5)
798
799
  test.assertAlmostEqual(V[2][0], expected_V[2][0], places=5)
799
800
  test.assertAlmostEqual(V[2][1], expected_V[2][1], places=5)
@@ -17,24 +17,17 @@ import unittest
17
17
  from typing import List, Tuple
18
18
 
19
19
  import warp as wp
20
- from warp.tests.unittest_utils import *
21
-
22
-
23
- def test_context_type_str(test, device):
24
- assert wp.context.type_str(List[int]) == "List[int]"
25
- assert wp.context.type_str(List[float]) == "List[float]"
26
-
27
- assert wp.context.type_str(Tuple[int]) == "Tuple[int]"
28
- assert wp.context.type_str(Tuple[float]) == "Tuple[float]"
29
- assert wp.context.type_str(Tuple[int, float]) == "Tuple[int, float]"
30
- assert wp.context.type_str(Tuple[int, ...]) == "Tuple[int, ...]"
31
20
 
32
21
 
33
22
  class TestContext(unittest.TestCase):
34
- pass
35
-
36
-
37
- add_function_test(TestContext, "test_context_type_str", test_context_type_str)
23
+ def test_context_type_str(self):
24
+ self.assertEqual(wp.context.type_str(List[int]), "List[int]")
25
+ self.assertEqual(wp.context.type_str(List[float]), "List[float]")
26
+
27
+ self.assertEqual(wp.context.type_str(Tuple[int]), "Tuple[int]")
28
+ self.assertEqual(wp.context.type_str(Tuple[float]), "Tuple[float]")
29
+ self.assertEqual(wp.context.type_str(Tuple[int, float]), "Tuple[int, float]")
30
+ self.assertEqual(wp.context.type_str(Tuple[int, ...]), "Tuple[int, ...]")
38
31
 
39
32
 
40
33
  if __name__ == "__main__":