warp-lang 1.8.1__py3-none-macosx_10_13_universal2.whl → 1.9.1__py3-none-macosx_10_13_universal2.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.
- warp/__init__.py +282 -103
- warp/__init__.pyi +1904 -114
- warp/bin/libwarp-clang.dylib +0 -0
- warp/bin/libwarp.dylib +0 -0
- warp/build.py +93 -30
- warp/build_dll.py +331 -101
- warp/builtins.py +1244 -160
- warp/codegen.py +317 -206
- warp/config.py +1 -1
- warp/context.py +1465 -789
- warp/examples/core/example_marching_cubes.py +1 -0
- warp/examples/core/example_render_opengl.py +100 -3
- warp/examples/fem/example_apic_fluid.py +98 -52
- warp/examples/fem/example_convection_diffusion_dg.py +25 -4
- warp/examples/fem/example_diffusion_mgpu.py +8 -3
- warp/examples/fem/utils.py +68 -22
- warp/examples/interop/example_jax_kernel.py +2 -1
- warp/fabric.py +1 -1
- warp/fem/cache.py +27 -19
- warp/fem/domain.py +2 -2
- warp/fem/field/nodal_field.py +2 -2
- warp/fem/field/virtual.py +264 -166
- warp/fem/geometry/geometry.py +5 -5
- warp/fem/integrate.py +129 -51
- warp/fem/space/restriction.py +4 -0
- warp/fem/space/shape/tet_shape_function.py +3 -10
- warp/jax_experimental/custom_call.py +25 -2
- warp/jax_experimental/ffi.py +22 -1
- warp/jax_experimental/xla_ffi.py +16 -7
- warp/marching_cubes.py +708 -0
- warp/native/array.h +99 -4
- warp/native/builtin.h +86 -9
- warp/native/bvh.cpp +64 -28
- warp/native/bvh.cu +58 -58
- warp/native/bvh.h +2 -2
- warp/native/clang/clang.cpp +7 -7
- warp/native/coloring.cpp +8 -2
- warp/native/crt.cpp +2 -2
- warp/native/crt.h +3 -5
- warp/native/cuda_util.cpp +41 -10
- warp/native/cuda_util.h +10 -4
- warp/native/exports.h +1842 -1908
- warp/native/fabric.h +2 -1
- warp/native/hashgrid.cpp +37 -37
- warp/native/hashgrid.cu +2 -2
- warp/native/initializer_array.h +1 -1
- warp/native/intersect.h +2 -2
- warp/native/mat.h +1910 -116
- warp/native/mathdx.cpp +43 -43
- warp/native/mesh.cpp +24 -24
- warp/native/mesh.cu +26 -26
- warp/native/mesh.h +4 -2
- warp/native/nanovdb/GridHandle.h +179 -12
- warp/native/nanovdb/HostBuffer.h +8 -7
- warp/native/nanovdb/NanoVDB.h +517 -895
- warp/native/nanovdb/NodeManager.h +323 -0
- warp/native/nanovdb/PNanoVDB.h +2 -2
- warp/native/quat.h +331 -14
- warp/native/range.h +7 -1
- warp/native/reduce.cpp +10 -10
- warp/native/reduce.cu +13 -14
- warp/native/runlength_encode.cpp +2 -2
- warp/native/runlength_encode.cu +5 -5
- warp/native/scan.cpp +3 -3
- warp/native/scan.cu +4 -4
- warp/native/sort.cpp +10 -10
- warp/native/sort.cu +40 -31
- warp/native/sort.h +2 -0
- warp/native/sparse.cpp +8 -8
- warp/native/sparse.cu +13 -13
- warp/native/spatial.h +366 -17
- warp/native/temp_buffer.h +2 -2
- warp/native/tile.h +471 -82
- warp/native/vec.h +328 -14
- warp/native/volume.cpp +54 -54
- warp/native/volume.cu +1 -1
- warp/native/volume.h +2 -1
- warp/native/volume_builder.cu +30 -37
- warp/native/warp.cpp +150 -149
- warp/native/warp.cu +377 -216
- warp/native/warp.h +227 -226
- warp/optim/linear.py +736 -271
- warp/render/imgui_manager.py +289 -0
- warp/render/render_opengl.py +99 -18
- warp/render/render_usd.py +1 -0
- warp/sim/graph_coloring.py +2 -2
- warp/sparse.py +558 -175
- warp/tests/aux_test_module_aot.py +7 -0
- warp/tests/cuda/test_async.py +3 -3
- warp/tests/cuda/test_conditional_captures.py +101 -0
- warp/tests/geometry/test_hash_grid.py +38 -0
- warp/tests/geometry/test_marching_cubes.py +233 -12
- warp/tests/interop/test_jax.py +608 -28
- warp/tests/sim/test_coloring.py +6 -6
- warp/tests/test_array.py +58 -5
- warp/tests/test_codegen.py +4 -3
- warp/tests/test_context.py +8 -15
- warp/tests/test_enum.py +136 -0
- warp/tests/test_examples.py +2 -2
- warp/tests/test_fem.py +49 -6
- warp/tests/test_fixedarray.py +229 -0
- warp/tests/test_func.py +18 -15
- warp/tests/test_future_annotations.py +7 -5
- warp/tests/test_linear_solvers.py +30 -0
- warp/tests/test_map.py +15 -1
- warp/tests/test_mat.py +1518 -378
- warp/tests/test_mat_assign_copy.py +178 -0
- warp/tests/test_mat_constructors.py +574 -0
- warp/tests/test_module_aot.py +287 -0
- warp/tests/test_print.py +69 -0
- warp/tests/test_quat.py +140 -34
- warp/tests/test_quat_assign_copy.py +145 -0
- warp/tests/test_reload.py +2 -1
- warp/tests/test_sparse.py +71 -0
- warp/tests/test_spatial.py +140 -34
- warp/tests/test_spatial_assign_copy.py +160 -0
- warp/tests/test_struct.py +43 -3
- warp/tests/test_tuple.py +96 -0
- warp/tests/test_types.py +61 -20
- warp/tests/test_vec.py +179 -34
- warp/tests/test_vec_assign_copy.py +143 -0
- warp/tests/tile/test_tile.py +245 -18
- warp/tests/tile/test_tile_cholesky.py +605 -0
- warp/tests/tile/test_tile_load.py +169 -0
- warp/tests/tile/test_tile_mathdx.py +2 -558
- warp/tests/tile/test_tile_matmul.py +1 -1
- warp/tests/tile/test_tile_mlp.py +1 -1
- warp/tests/tile/test_tile_shared_memory.py +5 -5
- warp/tests/unittest_suites.py +6 -0
- warp/tests/walkthrough_debug.py +1 -1
- warp/thirdparty/unittest_parallel.py +108 -9
- warp/types.py +571 -267
- warp/utils.py +68 -86
- {warp_lang-1.8.1.dist-info → warp_lang-1.9.1.dist-info}/METADATA +29 -69
- {warp_lang-1.8.1.dist-info → warp_lang-1.9.1.dist-info}/RECORD +138 -128
- warp/native/marching.cpp +0 -19
- warp/native/marching.cu +0 -514
- warp/native/marching.h +0 -19
- {warp_lang-1.8.1.dist-info → warp_lang-1.9.1.dist-info}/WHEEL +0 -0
- {warp_lang-1.8.1.dist-info → warp_lang-1.9.1.dist-info}/licenses/LICENSE.md +0 -0
- {warp_lang-1.8.1.dist-info → warp_lang-1.9.1.dist-info}/top_level.txt +0 -0
warp/tests/cuda/test_async.py
CHANGED
|
@@ -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.
|
|
430
|
-
wp.context.runtime.core.
|
|
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.
|
|
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()
|
|
@@ -191,7 +191,44 @@ def test_hashgrid_inputs(test, device):
|
|
|
191
191
|
assert_array_equal(counts_ndim, counts_ref)
|
|
192
192
|
|
|
193
193
|
|
|
194
|
+
def test_hashgrid_multiple_streams(test, device):
|
|
195
|
+
with wp.ScopedDevice(device):
|
|
196
|
+
points = particle_grid(16, 32, 16, (0.0, 0.3, 0.0), cell_radius * 0.25, 0.1)
|
|
197
|
+
points_ref = wp.array(points, dtype=wp.vec3)
|
|
198
|
+
counts_ref = wp.zeros(len(points), dtype=int)
|
|
199
|
+
|
|
200
|
+
grid_dim = 64
|
|
201
|
+
grid_ref = wp.HashGrid(grid_dim, grid_dim, grid_dim)
|
|
202
|
+
grid_ref.build(points_ref, cell_radius)
|
|
203
|
+
|
|
204
|
+
# get reference counts
|
|
205
|
+
wp.launch(kernel=count_neighbors, dim=len(points), inputs=[grid_ref.id, query_radius, points_ref, counts_ref])
|
|
206
|
+
|
|
207
|
+
# create multiple streams
|
|
208
|
+
num_streams = 10
|
|
209
|
+
streams = [wp.Stream(device=device) for _ in range(num_streams)]
|
|
210
|
+
counts_per_stream = [wp.zeros(len(points), dtype=int) for _ in range(num_streams)]
|
|
211
|
+
|
|
212
|
+
# test whether HashGrid and radix sort work with multiple streams without race conditions
|
|
213
|
+
for i in range(num_streams):
|
|
214
|
+
with wp.ScopedStream(streams[i]):
|
|
215
|
+
grid = wp.HashGrid(grid_dim, grid_dim, grid_dim)
|
|
216
|
+
grid.build(points_ref, cell_radius)
|
|
217
|
+
|
|
218
|
+
# get counts for this stream
|
|
219
|
+
wp.launch(
|
|
220
|
+
kernel=count_neighbors,
|
|
221
|
+
dim=len(points),
|
|
222
|
+
inputs=[grid.id, query_radius, points_ref, counts_per_stream[i]],
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# run this loop after all streams are scheduled to ensure asynchronous behaviour above
|
|
226
|
+
for i in range(num_streams):
|
|
227
|
+
assert_array_equal(counts_per_stream[i], counts_ref)
|
|
228
|
+
|
|
229
|
+
|
|
194
230
|
devices = get_test_devices()
|
|
231
|
+
cuda_devices = get_cuda_test_devices()
|
|
195
232
|
|
|
196
233
|
|
|
197
234
|
class TestHashGrid(unittest.TestCase):
|
|
@@ -214,6 +251,7 @@ class TestHashGrid(unittest.TestCase):
|
|
|
214
251
|
|
|
215
252
|
add_function_test(TestHashGrid, "test_hashgrid_query", test_hashgrid_query, devices=devices)
|
|
216
253
|
add_function_test(TestHashGrid, "test_hashgrid_inputs", test_hashgrid_inputs, devices=devices)
|
|
254
|
+
add_function_test(TestHashGrid, "test_hashgrid_multiple_streams", test_hashgrid_multiple_streams, devices=cuda_devices)
|
|
217
255
|
|
|
218
256
|
|
|
219
257
|
if __name__ == "__main__":
|
|
@@ -22,7 +22,9 @@ from warp.tests.unittest_utils import *
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@wp.kernel
|
|
25
|
-
def
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
wp.atomic_add(out_area, 0, area)
|
|
43
78
|
|
|
44
|
-
radius = dim / 4.0
|
|
45
79
|
|
|
46
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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__":
|