warp-lang 1.10.0__py3-none-macosx_11_0_arm64.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 (468) hide show
  1. warp/__init__.py +334 -0
  2. warp/__init__.pyi +5856 -0
  3. warp/_src/__init__.py +14 -0
  4. warp/_src/autograd.py +1077 -0
  5. warp/_src/build.py +620 -0
  6. warp/_src/build_dll.py +642 -0
  7. warp/_src/builtins.py +10555 -0
  8. warp/_src/codegen.py +4361 -0
  9. warp/_src/config.py +178 -0
  10. warp/_src/constants.py +59 -0
  11. warp/_src/context.py +8352 -0
  12. warp/_src/dlpack.py +464 -0
  13. warp/_src/fabric.py +362 -0
  14. warp/_src/fem/__init__.py +14 -0
  15. warp/_src/fem/adaptivity.py +510 -0
  16. warp/_src/fem/cache.py +689 -0
  17. warp/_src/fem/dirichlet.py +190 -0
  18. warp/_src/fem/domain.py +553 -0
  19. warp/_src/fem/field/__init__.py +131 -0
  20. warp/_src/fem/field/field.py +703 -0
  21. warp/_src/fem/field/nodal_field.py +403 -0
  22. warp/_src/fem/field/restriction.py +39 -0
  23. warp/_src/fem/field/virtual.py +1021 -0
  24. warp/_src/fem/geometry/__init__.py +32 -0
  25. warp/_src/fem/geometry/adaptive_nanogrid.py +782 -0
  26. warp/_src/fem/geometry/closest_point.py +99 -0
  27. warp/_src/fem/geometry/deformed_geometry.py +277 -0
  28. warp/_src/fem/geometry/element.py +854 -0
  29. warp/_src/fem/geometry/geometry.py +693 -0
  30. warp/_src/fem/geometry/grid_2d.py +478 -0
  31. warp/_src/fem/geometry/grid_3d.py +539 -0
  32. warp/_src/fem/geometry/hexmesh.py +956 -0
  33. warp/_src/fem/geometry/nanogrid.py +660 -0
  34. warp/_src/fem/geometry/partition.py +483 -0
  35. warp/_src/fem/geometry/quadmesh.py +597 -0
  36. warp/_src/fem/geometry/tetmesh.py +762 -0
  37. warp/_src/fem/geometry/trimesh.py +588 -0
  38. warp/_src/fem/integrate.py +2507 -0
  39. warp/_src/fem/linalg.py +385 -0
  40. warp/_src/fem/operator.py +398 -0
  41. warp/_src/fem/polynomial.py +231 -0
  42. warp/_src/fem/quadrature/__init__.py +17 -0
  43. warp/_src/fem/quadrature/pic_quadrature.py +318 -0
  44. warp/_src/fem/quadrature/quadrature.py +665 -0
  45. warp/_src/fem/space/__init__.py +248 -0
  46. warp/_src/fem/space/basis_function_space.py +499 -0
  47. warp/_src/fem/space/basis_space.py +681 -0
  48. warp/_src/fem/space/dof_mapper.py +253 -0
  49. warp/_src/fem/space/function_space.py +312 -0
  50. warp/_src/fem/space/grid_2d_function_space.py +179 -0
  51. warp/_src/fem/space/grid_3d_function_space.py +229 -0
  52. warp/_src/fem/space/hexmesh_function_space.py +255 -0
  53. warp/_src/fem/space/nanogrid_function_space.py +199 -0
  54. warp/_src/fem/space/partition.py +435 -0
  55. warp/_src/fem/space/quadmesh_function_space.py +222 -0
  56. warp/_src/fem/space/restriction.py +221 -0
  57. warp/_src/fem/space/shape/__init__.py +152 -0
  58. warp/_src/fem/space/shape/cube_shape_function.py +1107 -0
  59. warp/_src/fem/space/shape/shape_function.py +134 -0
  60. warp/_src/fem/space/shape/square_shape_function.py +928 -0
  61. warp/_src/fem/space/shape/tet_shape_function.py +829 -0
  62. warp/_src/fem/space/shape/triangle_shape_function.py +674 -0
  63. warp/_src/fem/space/tetmesh_function_space.py +270 -0
  64. warp/_src/fem/space/topology.py +461 -0
  65. warp/_src/fem/space/trimesh_function_space.py +193 -0
  66. warp/_src/fem/types.py +114 -0
  67. warp/_src/fem/utils.py +488 -0
  68. warp/_src/jax.py +188 -0
  69. warp/_src/jax_experimental/__init__.py +14 -0
  70. warp/_src/jax_experimental/custom_call.py +389 -0
  71. warp/_src/jax_experimental/ffi.py +1286 -0
  72. warp/_src/jax_experimental/xla_ffi.py +658 -0
  73. warp/_src/marching_cubes.py +710 -0
  74. warp/_src/math.py +416 -0
  75. warp/_src/optim/__init__.py +14 -0
  76. warp/_src/optim/adam.py +165 -0
  77. warp/_src/optim/linear.py +1608 -0
  78. warp/_src/optim/sgd.py +114 -0
  79. warp/_src/paddle.py +408 -0
  80. warp/_src/render/__init__.py +14 -0
  81. warp/_src/render/imgui_manager.py +291 -0
  82. warp/_src/render/render_opengl.py +3638 -0
  83. warp/_src/render/render_usd.py +939 -0
  84. warp/_src/render/utils.py +162 -0
  85. warp/_src/sparse.py +2718 -0
  86. warp/_src/tape.py +1208 -0
  87. warp/_src/thirdparty/__init__.py +0 -0
  88. warp/_src/thirdparty/appdirs.py +598 -0
  89. warp/_src/thirdparty/dlpack.py +145 -0
  90. warp/_src/thirdparty/unittest_parallel.py +676 -0
  91. warp/_src/torch.py +393 -0
  92. warp/_src/types.py +5888 -0
  93. warp/_src/utils.py +1695 -0
  94. warp/autograd.py +33 -0
  95. warp/bin/libwarp-clang.dylib +0 -0
  96. warp/bin/libwarp.dylib +0 -0
  97. warp/build.py +29 -0
  98. warp/build_dll.py +24 -0
  99. warp/codegen.py +24 -0
  100. warp/constants.py +24 -0
  101. warp/context.py +33 -0
  102. warp/dlpack.py +24 -0
  103. warp/examples/__init__.py +24 -0
  104. warp/examples/assets/bear.usd +0 -0
  105. warp/examples/assets/bunny.usd +0 -0
  106. warp/examples/assets/cube.usd +0 -0
  107. warp/examples/assets/nonuniform.usd +0 -0
  108. warp/examples/assets/nvidia_logo.png +0 -0
  109. warp/examples/assets/pixel.jpg +0 -0
  110. warp/examples/assets/rocks.nvdb +0 -0
  111. warp/examples/assets/rocks.usd +0 -0
  112. warp/examples/assets/sphere.usd +0 -0
  113. warp/examples/assets/square_cloth.usd +0 -0
  114. warp/examples/benchmarks/benchmark_api.py +389 -0
  115. warp/examples/benchmarks/benchmark_cloth.py +296 -0
  116. warp/examples/benchmarks/benchmark_cloth_cupy.py +96 -0
  117. warp/examples/benchmarks/benchmark_cloth_jax.py +105 -0
  118. warp/examples/benchmarks/benchmark_cloth_numba.py +161 -0
  119. warp/examples/benchmarks/benchmark_cloth_numpy.py +85 -0
  120. warp/examples/benchmarks/benchmark_cloth_paddle.py +94 -0
  121. warp/examples/benchmarks/benchmark_cloth_pytorch.py +94 -0
  122. warp/examples/benchmarks/benchmark_cloth_taichi.py +120 -0
  123. warp/examples/benchmarks/benchmark_cloth_warp.py +153 -0
  124. warp/examples/benchmarks/benchmark_gemm.py +164 -0
  125. warp/examples/benchmarks/benchmark_interop_paddle.py +166 -0
  126. warp/examples/benchmarks/benchmark_interop_torch.py +166 -0
  127. warp/examples/benchmarks/benchmark_launches.py +301 -0
  128. warp/examples/benchmarks/benchmark_tile_load_store.py +103 -0
  129. warp/examples/benchmarks/benchmark_tile_sort.py +155 -0
  130. warp/examples/browse.py +37 -0
  131. warp/examples/core/example_cupy.py +86 -0
  132. warp/examples/core/example_dem.py +241 -0
  133. warp/examples/core/example_fluid.py +299 -0
  134. warp/examples/core/example_graph_capture.py +150 -0
  135. warp/examples/core/example_marching_cubes.py +195 -0
  136. warp/examples/core/example_mesh.py +180 -0
  137. warp/examples/core/example_mesh_intersect.py +211 -0
  138. warp/examples/core/example_nvdb.py +182 -0
  139. warp/examples/core/example_raycast.py +111 -0
  140. warp/examples/core/example_raymarch.py +205 -0
  141. warp/examples/core/example_render_opengl.py +290 -0
  142. warp/examples/core/example_sample_mesh.py +300 -0
  143. warp/examples/core/example_sph.py +411 -0
  144. warp/examples/core/example_spin_lock.py +93 -0
  145. warp/examples/core/example_torch.py +211 -0
  146. warp/examples/core/example_wave.py +269 -0
  147. warp/examples/core/example_work_queue.py +118 -0
  148. warp/examples/distributed/example_jacobi_mpi.py +506 -0
  149. warp/examples/fem/example_adaptive_grid.py +286 -0
  150. warp/examples/fem/example_apic_fluid.py +469 -0
  151. warp/examples/fem/example_burgers.py +261 -0
  152. warp/examples/fem/example_convection_diffusion.py +181 -0
  153. warp/examples/fem/example_convection_diffusion_dg.py +225 -0
  154. warp/examples/fem/example_darcy_ls_optimization.py +489 -0
  155. warp/examples/fem/example_deformed_geometry.py +172 -0
  156. warp/examples/fem/example_diffusion.py +196 -0
  157. warp/examples/fem/example_diffusion_3d.py +225 -0
  158. warp/examples/fem/example_diffusion_mgpu.py +225 -0
  159. warp/examples/fem/example_distortion_energy.py +228 -0
  160. warp/examples/fem/example_elastic_shape_optimization.py +387 -0
  161. warp/examples/fem/example_magnetostatics.py +242 -0
  162. warp/examples/fem/example_mixed_elasticity.py +293 -0
  163. warp/examples/fem/example_navier_stokes.py +263 -0
  164. warp/examples/fem/example_nonconforming_contact.py +300 -0
  165. warp/examples/fem/example_stokes.py +213 -0
  166. warp/examples/fem/example_stokes_transfer.py +262 -0
  167. warp/examples/fem/example_streamlines.py +357 -0
  168. warp/examples/fem/utils.py +1047 -0
  169. warp/examples/interop/example_jax_callable.py +146 -0
  170. warp/examples/interop/example_jax_ffi_callback.py +132 -0
  171. warp/examples/interop/example_jax_kernel.py +232 -0
  172. warp/examples/optim/example_diffray.py +561 -0
  173. warp/examples/optim/example_fluid_checkpoint.py +497 -0
  174. warp/examples/tile/example_tile_block_cholesky.py +502 -0
  175. warp/examples/tile/example_tile_cholesky.py +88 -0
  176. warp/examples/tile/example_tile_convolution.py +66 -0
  177. warp/examples/tile/example_tile_fft.py +55 -0
  178. warp/examples/tile/example_tile_filtering.py +113 -0
  179. warp/examples/tile/example_tile_matmul.py +85 -0
  180. warp/examples/tile/example_tile_mcgp.py +191 -0
  181. warp/examples/tile/example_tile_mlp.py +385 -0
  182. warp/examples/tile/example_tile_nbody.py +199 -0
  183. warp/fabric.py +24 -0
  184. warp/fem/__init__.py +173 -0
  185. warp/fem/adaptivity.py +26 -0
  186. warp/fem/cache.py +30 -0
  187. warp/fem/dirichlet.py +24 -0
  188. warp/fem/field/__init__.py +24 -0
  189. warp/fem/field/field.py +26 -0
  190. warp/fem/geometry/__init__.py +21 -0
  191. warp/fem/geometry/closest_point.py +31 -0
  192. warp/fem/linalg.py +38 -0
  193. warp/fem/operator.py +32 -0
  194. warp/fem/polynomial.py +29 -0
  195. warp/fem/space/__init__.py +22 -0
  196. warp/fem/space/basis_space.py +24 -0
  197. warp/fem/space/shape/__init__.py +68 -0
  198. warp/fem/space/topology.py +24 -0
  199. warp/fem/types.py +24 -0
  200. warp/fem/utils.py +32 -0
  201. warp/jax.py +29 -0
  202. warp/jax_experimental/__init__.py +29 -0
  203. warp/jax_experimental/custom_call.py +29 -0
  204. warp/jax_experimental/ffi.py +39 -0
  205. warp/jax_experimental/xla_ffi.py +24 -0
  206. warp/marching_cubes.py +24 -0
  207. warp/math.py +37 -0
  208. warp/native/array.h +1687 -0
  209. warp/native/builtin.h +2327 -0
  210. warp/native/bvh.cpp +562 -0
  211. warp/native/bvh.cu +826 -0
  212. warp/native/bvh.h +555 -0
  213. warp/native/clang/clang.cpp +541 -0
  214. warp/native/coloring.cpp +622 -0
  215. warp/native/crt.cpp +51 -0
  216. warp/native/crt.h +568 -0
  217. warp/native/cuda_crt.h +1058 -0
  218. warp/native/cuda_util.cpp +677 -0
  219. warp/native/cuda_util.h +313 -0
  220. warp/native/error.cpp +77 -0
  221. warp/native/error.h +36 -0
  222. warp/native/exports.h +2023 -0
  223. warp/native/fabric.h +246 -0
  224. warp/native/hashgrid.cpp +311 -0
  225. warp/native/hashgrid.cu +89 -0
  226. warp/native/hashgrid.h +240 -0
  227. warp/native/initializer_array.h +41 -0
  228. warp/native/intersect.h +1253 -0
  229. warp/native/intersect_adj.h +375 -0
  230. warp/native/intersect_tri.h +348 -0
  231. warp/native/mat.h +5189 -0
  232. warp/native/mathdx.cpp +93 -0
  233. warp/native/matnn.h +221 -0
  234. warp/native/mesh.cpp +266 -0
  235. warp/native/mesh.cu +406 -0
  236. warp/native/mesh.h +2097 -0
  237. warp/native/nanovdb/GridHandle.h +533 -0
  238. warp/native/nanovdb/HostBuffer.h +591 -0
  239. warp/native/nanovdb/NanoVDB.h +6246 -0
  240. warp/native/nanovdb/NodeManager.h +323 -0
  241. warp/native/nanovdb/PNanoVDB.h +3390 -0
  242. warp/native/noise.h +859 -0
  243. warp/native/quat.h +1664 -0
  244. warp/native/rand.h +342 -0
  245. warp/native/range.h +145 -0
  246. warp/native/reduce.cpp +174 -0
  247. warp/native/reduce.cu +363 -0
  248. warp/native/runlength_encode.cpp +79 -0
  249. warp/native/runlength_encode.cu +61 -0
  250. warp/native/scan.cpp +47 -0
  251. warp/native/scan.cu +55 -0
  252. warp/native/scan.h +23 -0
  253. warp/native/solid_angle.h +466 -0
  254. warp/native/sort.cpp +251 -0
  255. warp/native/sort.cu +286 -0
  256. warp/native/sort.h +35 -0
  257. warp/native/sparse.cpp +241 -0
  258. warp/native/sparse.cu +435 -0
  259. warp/native/spatial.h +1306 -0
  260. warp/native/svd.h +727 -0
  261. warp/native/temp_buffer.h +46 -0
  262. warp/native/tile.h +4124 -0
  263. warp/native/tile_radix_sort.h +1112 -0
  264. warp/native/tile_reduce.h +838 -0
  265. warp/native/tile_scan.h +240 -0
  266. warp/native/tuple.h +189 -0
  267. warp/native/vec.h +2199 -0
  268. warp/native/version.h +23 -0
  269. warp/native/volume.cpp +501 -0
  270. warp/native/volume.cu +68 -0
  271. warp/native/volume.h +970 -0
  272. warp/native/volume_builder.cu +483 -0
  273. warp/native/volume_builder.h +52 -0
  274. warp/native/volume_impl.h +70 -0
  275. warp/native/warp.cpp +1143 -0
  276. warp/native/warp.cu +4604 -0
  277. warp/native/warp.h +358 -0
  278. warp/optim/__init__.py +20 -0
  279. warp/optim/adam.py +24 -0
  280. warp/optim/linear.py +35 -0
  281. warp/optim/sgd.py +24 -0
  282. warp/paddle.py +24 -0
  283. warp/py.typed +0 -0
  284. warp/render/__init__.py +22 -0
  285. warp/render/imgui_manager.py +29 -0
  286. warp/render/render_opengl.py +24 -0
  287. warp/render/render_usd.py +24 -0
  288. warp/render/utils.py +24 -0
  289. warp/sparse.py +51 -0
  290. warp/tape.py +24 -0
  291. warp/tests/__init__.py +1 -0
  292. warp/tests/__main__.py +4 -0
  293. warp/tests/assets/curlnoise_golden.npy +0 -0
  294. warp/tests/assets/mlp_golden.npy +0 -0
  295. warp/tests/assets/pixel.npy +0 -0
  296. warp/tests/assets/pnoise_golden.npy +0 -0
  297. warp/tests/assets/spiky.usd +0 -0
  298. warp/tests/assets/test_grid.nvdb +0 -0
  299. warp/tests/assets/test_index_grid.nvdb +0 -0
  300. warp/tests/assets/test_int32_grid.nvdb +0 -0
  301. warp/tests/assets/test_vec_grid.nvdb +0 -0
  302. warp/tests/assets/torus.nvdb +0 -0
  303. warp/tests/assets/torus.usda +105 -0
  304. warp/tests/aux_test_class_kernel.py +34 -0
  305. warp/tests/aux_test_compile_consts_dummy.py +18 -0
  306. warp/tests/aux_test_conditional_unequal_types_kernels.py +29 -0
  307. warp/tests/aux_test_dependent.py +29 -0
  308. warp/tests/aux_test_grad_customs.py +29 -0
  309. warp/tests/aux_test_instancing_gc.py +26 -0
  310. warp/tests/aux_test_module_aot.py +7 -0
  311. warp/tests/aux_test_module_unload.py +23 -0
  312. warp/tests/aux_test_name_clash1.py +40 -0
  313. warp/tests/aux_test_name_clash2.py +40 -0
  314. warp/tests/aux_test_reference.py +9 -0
  315. warp/tests/aux_test_reference_reference.py +8 -0
  316. warp/tests/aux_test_square.py +16 -0
  317. warp/tests/aux_test_unresolved_func.py +22 -0
  318. warp/tests/aux_test_unresolved_symbol.py +22 -0
  319. warp/tests/cuda/__init__.py +0 -0
  320. warp/tests/cuda/test_async.py +676 -0
  321. warp/tests/cuda/test_conditional_captures.py +1147 -0
  322. warp/tests/cuda/test_ipc.py +124 -0
  323. warp/tests/cuda/test_mempool.py +233 -0
  324. warp/tests/cuda/test_multigpu.py +169 -0
  325. warp/tests/cuda/test_peer.py +139 -0
  326. warp/tests/cuda/test_pinned.py +84 -0
  327. warp/tests/cuda/test_streams.py +691 -0
  328. warp/tests/geometry/__init__.py +0 -0
  329. warp/tests/geometry/test_bvh.py +335 -0
  330. warp/tests/geometry/test_hash_grid.py +259 -0
  331. warp/tests/geometry/test_marching_cubes.py +294 -0
  332. warp/tests/geometry/test_mesh.py +318 -0
  333. warp/tests/geometry/test_mesh_query_aabb.py +392 -0
  334. warp/tests/geometry/test_mesh_query_point.py +935 -0
  335. warp/tests/geometry/test_mesh_query_ray.py +323 -0
  336. warp/tests/geometry/test_volume.py +1103 -0
  337. warp/tests/geometry/test_volume_write.py +346 -0
  338. warp/tests/interop/__init__.py +0 -0
  339. warp/tests/interop/test_dlpack.py +730 -0
  340. warp/tests/interop/test_jax.py +1673 -0
  341. warp/tests/interop/test_paddle.py +800 -0
  342. warp/tests/interop/test_torch.py +1001 -0
  343. warp/tests/run_coverage_serial.py +39 -0
  344. warp/tests/test_adam.py +162 -0
  345. warp/tests/test_arithmetic.py +1096 -0
  346. warp/tests/test_array.py +3756 -0
  347. warp/tests/test_array_reduce.py +156 -0
  348. warp/tests/test_assert.py +303 -0
  349. warp/tests/test_atomic.py +336 -0
  350. warp/tests/test_atomic_bitwise.py +209 -0
  351. warp/tests/test_atomic_cas.py +312 -0
  352. warp/tests/test_bool.py +220 -0
  353. warp/tests/test_builtins_resolution.py +732 -0
  354. warp/tests/test_closest_point_edge_edge.py +327 -0
  355. warp/tests/test_codegen.py +974 -0
  356. warp/tests/test_codegen_instancing.py +1495 -0
  357. warp/tests/test_compile_consts.py +215 -0
  358. warp/tests/test_conditional.py +298 -0
  359. warp/tests/test_context.py +35 -0
  360. warp/tests/test_copy.py +319 -0
  361. warp/tests/test_ctypes.py +618 -0
  362. warp/tests/test_dense.py +73 -0
  363. warp/tests/test_devices.py +127 -0
  364. warp/tests/test_enum.py +136 -0
  365. warp/tests/test_examples.py +424 -0
  366. warp/tests/test_fabricarray.py +998 -0
  367. warp/tests/test_fast_math.py +72 -0
  368. warp/tests/test_fem.py +2204 -0
  369. warp/tests/test_fixedarray.py +229 -0
  370. warp/tests/test_fp16.py +136 -0
  371. warp/tests/test_func.py +501 -0
  372. warp/tests/test_future_annotations.py +100 -0
  373. warp/tests/test_generics.py +656 -0
  374. warp/tests/test_grad.py +893 -0
  375. warp/tests/test_grad_customs.py +339 -0
  376. warp/tests/test_grad_debug.py +341 -0
  377. warp/tests/test_implicit_init.py +411 -0
  378. warp/tests/test_import.py +45 -0
  379. warp/tests/test_indexedarray.py +1140 -0
  380. warp/tests/test_intersect.py +103 -0
  381. warp/tests/test_iter.py +76 -0
  382. warp/tests/test_large.py +177 -0
  383. warp/tests/test_launch.py +411 -0
  384. warp/tests/test_lerp.py +151 -0
  385. warp/tests/test_linear_solvers.py +223 -0
  386. warp/tests/test_lvalue.py +427 -0
  387. warp/tests/test_map.py +526 -0
  388. warp/tests/test_mat.py +3515 -0
  389. warp/tests/test_mat_assign_copy.py +178 -0
  390. warp/tests/test_mat_constructors.py +573 -0
  391. warp/tests/test_mat_lite.py +122 -0
  392. warp/tests/test_mat_scalar_ops.py +2913 -0
  393. warp/tests/test_math.py +212 -0
  394. warp/tests/test_module_aot.py +287 -0
  395. warp/tests/test_module_hashing.py +258 -0
  396. warp/tests/test_modules_lite.py +70 -0
  397. warp/tests/test_noise.py +252 -0
  398. warp/tests/test_operators.py +299 -0
  399. warp/tests/test_options.py +129 -0
  400. warp/tests/test_overwrite.py +551 -0
  401. warp/tests/test_print.py +408 -0
  402. warp/tests/test_quat.py +2653 -0
  403. warp/tests/test_quat_assign_copy.py +145 -0
  404. warp/tests/test_rand.py +339 -0
  405. warp/tests/test_reload.py +303 -0
  406. warp/tests/test_rounding.py +157 -0
  407. warp/tests/test_runlength_encode.py +196 -0
  408. warp/tests/test_scalar_ops.py +133 -0
  409. warp/tests/test_smoothstep.py +108 -0
  410. warp/tests/test_snippet.py +318 -0
  411. warp/tests/test_sparse.py +845 -0
  412. warp/tests/test_spatial.py +2859 -0
  413. warp/tests/test_spatial_assign_copy.py +160 -0
  414. warp/tests/test_special_values.py +361 -0
  415. warp/tests/test_static.py +640 -0
  416. warp/tests/test_struct.py +901 -0
  417. warp/tests/test_tape.py +242 -0
  418. warp/tests/test_transient_module.py +93 -0
  419. warp/tests/test_triangle_closest_point.py +192 -0
  420. warp/tests/test_tuple.py +361 -0
  421. warp/tests/test_types.py +615 -0
  422. warp/tests/test_utils.py +594 -0
  423. warp/tests/test_vec.py +1408 -0
  424. warp/tests/test_vec_assign_copy.py +143 -0
  425. warp/tests/test_vec_constructors.py +325 -0
  426. warp/tests/test_vec_lite.py +80 -0
  427. warp/tests/test_vec_scalar_ops.py +2327 -0
  428. warp/tests/test_verify_fp.py +100 -0
  429. warp/tests/test_version.py +75 -0
  430. warp/tests/tile/__init__.py +0 -0
  431. warp/tests/tile/test_tile.py +1519 -0
  432. warp/tests/tile/test_tile_atomic_bitwise.py +403 -0
  433. warp/tests/tile/test_tile_cholesky.py +608 -0
  434. warp/tests/tile/test_tile_load.py +724 -0
  435. warp/tests/tile/test_tile_mathdx.py +156 -0
  436. warp/tests/tile/test_tile_matmul.py +179 -0
  437. warp/tests/tile/test_tile_mlp.py +400 -0
  438. warp/tests/tile/test_tile_reduce.py +950 -0
  439. warp/tests/tile/test_tile_shared_memory.py +376 -0
  440. warp/tests/tile/test_tile_sort.py +121 -0
  441. warp/tests/tile/test_tile_view.py +173 -0
  442. warp/tests/unittest_serial.py +47 -0
  443. warp/tests/unittest_suites.py +430 -0
  444. warp/tests/unittest_utils.py +469 -0
  445. warp/tests/walkthrough_debug.py +95 -0
  446. warp/torch.py +24 -0
  447. warp/types.py +51 -0
  448. warp/utils.py +31 -0
  449. warp_lang-1.10.0.dist-info/METADATA +459 -0
  450. warp_lang-1.10.0.dist-info/RECORD +468 -0
  451. warp_lang-1.10.0.dist-info/WHEEL +5 -0
  452. warp_lang-1.10.0.dist-info/licenses/LICENSE.md +176 -0
  453. warp_lang-1.10.0.dist-info/licenses/licenses/Gaia-LICENSE.txt +6 -0
  454. warp_lang-1.10.0.dist-info/licenses/licenses/appdirs-LICENSE.txt +22 -0
  455. warp_lang-1.10.0.dist-info/licenses/licenses/asset_pixel_jpg-LICENSE.txt +3 -0
  456. warp_lang-1.10.0.dist-info/licenses/licenses/cuda-LICENSE.txt +1582 -0
  457. warp_lang-1.10.0.dist-info/licenses/licenses/dlpack-LICENSE.txt +201 -0
  458. warp_lang-1.10.0.dist-info/licenses/licenses/fp16-LICENSE.txt +28 -0
  459. warp_lang-1.10.0.dist-info/licenses/licenses/libmathdx-LICENSE.txt +220 -0
  460. warp_lang-1.10.0.dist-info/licenses/licenses/llvm-LICENSE.txt +279 -0
  461. warp_lang-1.10.0.dist-info/licenses/licenses/moller-LICENSE.txt +16 -0
  462. warp_lang-1.10.0.dist-info/licenses/licenses/nanovdb-LICENSE.txt +2 -0
  463. warp_lang-1.10.0.dist-info/licenses/licenses/nvrtc-LICENSE.txt +1592 -0
  464. warp_lang-1.10.0.dist-info/licenses/licenses/svd-LICENSE.txt +23 -0
  465. warp_lang-1.10.0.dist-info/licenses/licenses/unittest_parallel-LICENSE.txt +21 -0
  466. warp_lang-1.10.0.dist-info/licenses/licenses/usd-LICENSE.txt +213 -0
  467. warp_lang-1.10.0.dist-info/licenses/licenses/windingnumber-LICENSE.txt +21 -0
  468. warp_lang-1.10.0.dist-info/top_level.txt +1 -0
warp/tests/test_fem.py ADDED
@@ -0,0 +1,2204 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import math
17
+ import platform
18
+ import unittest
19
+ from typing import Any
20
+
21
+ import numpy as np
22
+
23
+ import warp as wp
24
+ import warp.fem as fem
25
+ from warp._src.fem.geometry.closest_point import project_on_tet_at_origin, project_on_tri_at_origin
26
+ from warp._src.fem.space import shape
27
+ from warp.fem import Coords, D, Domain, Field, Sample, curl, div, grad, integrand, make_free_sample, normal
28
+ from warp.fem.cache import dynamic_kernel
29
+ from warp.fem.linalg import inverse_qr, spherical_part, symmetric_eigenvalues_qr, symmetric_part
30
+ from warp.fem.utils import (
31
+ grid_to_hexes,
32
+ grid_to_quads,
33
+ grid_to_tets,
34
+ grid_to_tris,
35
+ )
36
+ from warp.sparse import bsr_set_zero, bsr_zeros
37
+ from warp.tests.unittest_utils import *
38
+
39
+ vec6f = wp.vec(length=6, dtype=float)
40
+ mat66f = wp.mat(shape=(6, 6), dtype=float)
41
+
42
+
43
+ @integrand
44
+ def linear_form(s: Sample, u: Field):
45
+ return u(s)
46
+
47
+
48
+ @integrand
49
+ def bilinear_form(s: Sample, u: Field, v: Field):
50
+ return u(s) * v(s)
51
+
52
+
53
+ @integrand
54
+ def scaled_linear_form(s: Sample, u: Field, scale: wp.array(dtype=float)):
55
+ return u(s) * scale[0]
56
+
57
+
58
+ @wp.kernel
59
+ def atomic_sum(v: wp.array(dtype=float), sum: wp.array(dtype=float)):
60
+ i = wp.tid()
61
+ wp.atomic_add(sum, 0, v[i])
62
+
63
+
64
+ def test_integrate_gradient(test, device):
65
+ with wp.ScopedDevice(device):
66
+ # Grid geometry
67
+ geo = fem.Grid2D(res=wp.vec2i(5))
68
+
69
+ # Domain and function spaces
70
+ domain = fem.Cells(geometry=geo)
71
+ quadrature = fem.RegularQuadrature(domain=domain, order=3)
72
+
73
+ scalar_space = fem.make_polynomial_space(geo, degree=3)
74
+
75
+ u = scalar_space.make_field()
76
+ u.dof_values = wp.zeros_like(u.dof_values, requires_grad=True)
77
+
78
+ result = wp.empty(dtype=wp.float32, shape=(1), requires_grad=True)
79
+ tape = wp.Tape()
80
+
81
+ # forward pass
82
+ with tape:
83
+ fem.integrate(linear_form, quadrature=quadrature, fields={"u": u}, output=result)
84
+ tape.backward(result)
85
+
86
+ test_field = fem.make_test(space=scalar_space, domain=domain)
87
+
88
+ u_adj = wp.empty_like(u.dof_values, requires_grad=True)
89
+ scale = wp.ones(1, requires_grad=True)
90
+ loss = wp.zeros(1, requires_grad=True)
91
+
92
+ tape2 = wp.Tape()
93
+ with tape2:
94
+ fem.integrate(
95
+ scaled_linear_form,
96
+ quadrature=quadrature,
97
+ fields={"u": test_field},
98
+ values={"scale": scale},
99
+ assembly="generic",
100
+ output=u_adj,
101
+ )
102
+ wp.launch(atomic_sum, dim=u_adj.shape, inputs=[u_adj, loss])
103
+
104
+ # gradient of scalar integral w.r.t dofs should be equal to linear form vector
105
+ assert_np_equal(u_adj.numpy(), u.dof_values.grad.numpy(), tol=1.0e-8)
106
+ test.assertAlmostEqual(loss.numpy()[0], 1.0, places=4)
107
+
108
+ # Check gradient of linear form vec w.r.t value params
109
+ tape.zero()
110
+ tape2.backward(loss=loss)
111
+
112
+ test.assertAlmostEqual(loss.numpy()[0], scale.grad.numpy()[0], places=4)
113
+ tape2.zero()
114
+ test.assertEqual(scale.grad.numpy()[0], 0.0)
115
+
116
+ # Same, with dispatched assembly
117
+ tape2.reset()
118
+ loss.zero_()
119
+ with tape2:
120
+ fem.integrate(
121
+ scaled_linear_form,
122
+ quadrature=quadrature,
123
+ fields={"u": test_field},
124
+ values={"scale": scale},
125
+ assembly="dispatch",
126
+ output=u_adj,
127
+ )
128
+ wp.launch(atomic_sum, dim=u_adj.shape, inputs=[u_adj, loss])
129
+ tape2.backward(loss=loss)
130
+ test.assertAlmostEqual(loss.numpy()[0], scale.grad.numpy()[0], places=4)
131
+
132
+
133
+ @fem.integrand
134
+ def bilinear_field(s: fem.Sample, domain: fem.Domain):
135
+ x = domain(s)
136
+ return x[0] * x[1]
137
+
138
+
139
+ @fem.integrand
140
+ def grad_field(s: fem.Sample, p: fem.Field):
141
+ return fem.grad(p, s)
142
+
143
+
144
+ def test_interpolate_gradient(test, device):
145
+ with wp.ScopedDevice(device):
146
+ # Quad mesh with single element
147
+ # so we can test gradient with respect to vertex positions
148
+ positions = wp.array([[0.0, 0.0], [0.0, 2.0], [2.0, 0.0], [2.0, 2.0]], dtype=wp.vec2, requires_grad=True)
149
+ quads = wp.array([[0, 2, 3, 1]], dtype=int)
150
+ geo = fem.Quadmesh2D(quads, positions)
151
+
152
+ # Quadratic scalar space
153
+ scalar_space = fem.make_polynomial_space(geo, degree=2)
154
+
155
+ # Point-based vector space
156
+ # So we can test gradient with respect to interpolation point position
157
+ point_coords = wp.array([[[0.5, 0.5, 0.0]]], dtype=fem.Coords, requires_grad=True)
158
+ point_quadrature = fem.ExplicitQuadrature(
159
+ domain=fem.Cells(geo), points=point_coords, weights=wp.array([[1.0]], dtype=float)
160
+ )
161
+ interpolation_nodes = fem.PointBasisSpace(point_quadrature)
162
+ vector_space = fem.make_collocated_function_space(interpolation_nodes, dtype=wp.vec2)
163
+
164
+ # Initialize scalar field with known function
165
+ scalar_field = scalar_space.make_field()
166
+ scalar_field.dof_values.requires_grad = True
167
+ fem.interpolate(bilinear_field, dest=scalar_field)
168
+
169
+ # Interpolate gradient at center point
170
+ vector_field = vector_space.make_field()
171
+ vector_field.dof_values.requires_grad = True
172
+ vector_field_restriction = fem.make_restriction(vector_field)
173
+ tape = wp.Tape()
174
+ with tape:
175
+ fem.interpolate(
176
+ grad_field,
177
+ dest=vector_field_restriction,
178
+ fields={"p": scalar_field},
179
+ kernel_options={"enable_backward": True},
180
+ )
181
+
182
+ assert_np_equal(vector_field.dof_values.numpy(), np.array([[1.0, 1.0]]))
183
+
184
+ vector_field.dof_values.grad.assign([1.0, 0.0])
185
+ tape.backward()
186
+
187
+ assert_np_equal(scalar_field.dof_values.grad.numpy(), np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.5]))
188
+ assert_np_equal(
189
+ geo.positions.grad.numpy(),
190
+ np.array(
191
+ [
192
+ [0.25, 0.25],
193
+ [0.25, 0.25],
194
+ [-0.25, -0.25],
195
+ [-0.25, -0.25],
196
+ ]
197
+ ),
198
+ )
199
+ assert_np_equal(point_coords.grad.numpy(), np.array([[[0.0, 2.0, 0.0]]]))
200
+
201
+ tape.zero()
202
+ scalar_field.dof_values.grad.zero_()
203
+ geo.positions.grad.zero_()
204
+ point_coords.grad.zero_()
205
+
206
+ vector_field.dof_values.grad.assign([0.0, 1.0])
207
+ tape.backward()
208
+
209
+ assert_np_equal(scalar_field.dof_values.grad.numpy(), np.array([0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.5, 0.0]))
210
+ assert_np_equal(
211
+ geo.positions.grad.numpy(),
212
+ np.array(
213
+ [
214
+ [0.25, 0.25],
215
+ [-0.25, -0.25],
216
+ [0.25, 0.25],
217
+ [-0.25, -0.25],
218
+ ]
219
+ ),
220
+ )
221
+ assert_np_equal(point_coords.grad.numpy(), np.array([[[2.0, 0.0, 0.0]]]))
222
+
223
+ # Compare against jacobian
224
+ scalar_trial = fem.make_trial(scalar_space)
225
+ jacobian = bsr_zeros(
226
+ rows_of_blocks=point_quadrature.total_point_count(),
227
+ cols_of_blocks=scalar_space.node_count(),
228
+ block_type=wp.mat(shape=(2, 1), dtype=float),
229
+ )
230
+ fem.interpolate(
231
+ grad_field,
232
+ dest=jacobian,
233
+ quadrature=point_quadrature,
234
+ fields={"p": scalar_trial},
235
+ kernel_options={"enable_backward": False},
236
+ )
237
+ assert jacobian.nnz_sync() == 4 # one non-zero per edge center
238
+ assert_np_equal((jacobian @ scalar_field.dof_values.grad).numpy(), [[0.0, 0.5]])
239
+
240
+
241
+ @integrand
242
+ def vector_divergence_form(s: Sample, u: Field, q: Field):
243
+ return div(u, s) * q(s)
244
+
245
+
246
+ @integrand
247
+ def vector_grad_form(s: Sample, u: Field, q: Field):
248
+ return wp.dot(u(s), grad(q, s))
249
+
250
+
251
+ @integrand
252
+ def vector_boundary_form(domain: Domain, s: Sample, u: Field, q: Field):
253
+ return wp.dot(u(s) * q(s), normal(domain, s))
254
+
255
+
256
+ def test_vector_divergence_theorem(test, device):
257
+ rng = np.random.default_rng(123)
258
+
259
+ with wp.ScopedDevice(device):
260
+ # Grid geometry
261
+ geo = fem.Grid2D(res=wp.vec2i(5))
262
+
263
+ # Domain and function spaces
264
+ interior = fem.Cells(geometry=geo)
265
+ boundary = fem.BoundarySides(geometry=geo)
266
+
267
+ vector_space = fem.make_polynomial_space(geo, degree=2, dtype=wp.vec2)
268
+ scalar_space = fem.make_polynomial_space(geo, degree=1, dtype=float)
269
+
270
+ u = vector_space.make_field()
271
+ u.dof_values = rng.random(size=(u.dof_values.shape[0], 2))
272
+
273
+ # Divergence theorem
274
+ constant_one = scalar_space.make_field()
275
+ constant_one.dof_values.fill_(1.0)
276
+
277
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=vector_space.degree)
278
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=vector_space.degree)
279
+ div_int = fem.integrate(
280
+ vector_divergence_form,
281
+ quadrature=interior_quadrature,
282
+ fields={"u": u, "q": constant_one},
283
+ kernel_options={"enable_backward": False},
284
+ )
285
+ boundary_int = fem.integrate(
286
+ vector_boundary_form,
287
+ quadrature=boundary_quadrature,
288
+ fields={"u": u.trace(), "q": constant_one.trace()},
289
+ kernel_options={"enable_backward": False},
290
+ )
291
+
292
+ test.assertAlmostEqual(div_int, boundary_int, places=5)
293
+
294
+ # Integration by parts
295
+ q = scalar_space.make_field()
296
+ q.dof_values = rng.random(size=q.dof_values.shape[0])
297
+
298
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=vector_space.degree + scalar_space.degree)
299
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=vector_space.degree + scalar_space.degree)
300
+ div_int = fem.integrate(
301
+ vector_divergence_form,
302
+ quadrature=interior_quadrature,
303
+ fields={"u": u, "q": q},
304
+ kernel_options={"enable_backward": False},
305
+ )
306
+ grad_int = fem.integrate(
307
+ vector_grad_form,
308
+ quadrature=interior_quadrature,
309
+ fields={"u": u, "q": q},
310
+ kernel_options={"enable_backward": False},
311
+ )
312
+ boundary_int = fem.integrate(
313
+ vector_boundary_form,
314
+ quadrature=boundary_quadrature,
315
+ fields={"u": u.trace(), "q": q.trace()},
316
+ kernel_options={"enable_backward": False},
317
+ )
318
+
319
+ test.assertAlmostEqual(div_int + grad_int, boundary_int, places=5)
320
+
321
+
322
+ @integrand
323
+ def tensor_divergence_form(s: Sample, tau: Field, v: Field):
324
+ return wp.dot(div(tau, s), v(s))
325
+
326
+
327
+ @integrand
328
+ def tensor_grad_form(s: Sample, tau: Field, v: Field):
329
+ return wp.ddot(wp.transpose(tau(s)), grad(v, s))
330
+
331
+
332
+ @integrand
333
+ def tensor_boundary_form(domain: Domain, s: Sample, tau: Field, v: Field):
334
+ return wp.dot(tau(s) * v(s), normal(domain, s))
335
+
336
+
337
+ def test_tensor_divergence_theorem(test, device):
338
+ rng = np.random.default_rng(123)
339
+
340
+ with wp.ScopedDevice(device):
341
+ # Grid geometry
342
+ geo = fem.Grid2D(res=wp.vec2i(5))
343
+
344
+ # Domain and function spaces
345
+ interior = fem.Cells(geometry=geo)
346
+ boundary = fem.BoundarySides(geometry=geo)
347
+
348
+ tensor_space = fem.make_polynomial_space(geo, degree=2, dtype=wp.mat22)
349
+ vector_space = fem.make_polynomial_space(geo, degree=1, dtype=wp.vec2)
350
+
351
+ tau = tensor_space.make_field()
352
+ tau.dof_values = rng.random(size=(tau.dof_values.shape[0], 2, 2))
353
+
354
+ # Divergence theorem
355
+ constant_vec = vector_space.make_field()
356
+ constant_vec.dof_values.fill_(wp.vec2(0.5, 2.0))
357
+
358
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=tensor_space.degree)
359
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=tensor_space.degree)
360
+ div_int = fem.integrate(
361
+ tensor_divergence_form,
362
+ quadrature=interior_quadrature,
363
+ fields={"tau": tau, "v": constant_vec},
364
+ kernel_options={"enable_backward": False},
365
+ )
366
+ boundary_int = fem.integrate(
367
+ tensor_boundary_form,
368
+ quadrature=boundary_quadrature,
369
+ fields={"tau": tau.trace(), "v": constant_vec.trace()},
370
+ kernel_options={"enable_backward": False},
371
+ )
372
+
373
+ test.assertAlmostEqual(div_int, boundary_int, places=5)
374
+
375
+ # Integration by parts
376
+ v = vector_space.make_field()
377
+ v.dof_values = rng.random(size=(v.dof_values.shape[0], 2))
378
+
379
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=tensor_space.degree + vector_space.degree)
380
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=tensor_space.degree + vector_space.degree)
381
+ div_int = fem.integrate(
382
+ tensor_divergence_form,
383
+ quadrature=interior_quadrature,
384
+ fields={"tau": tau, "v": v},
385
+ kernel_options={"enable_backward": False},
386
+ )
387
+ grad_int = fem.integrate(
388
+ tensor_grad_form,
389
+ quadrature=interior_quadrature,
390
+ fields={"tau": tau, "v": v},
391
+ kernel_options={"enable_backward": False},
392
+ )
393
+ boundary_int = fem.integrate(
394
+ tensor_boundary_form,
395
+ quadrature=boundary_quadrature,
396
+ fields={"tau": tau.trace(), "v": v.trace()},
397
+ kernel_options={"enable_backward": False},
398
+ )
399
+
400
+ test.assertAlmostEqual(div_int + grad_int, boundary_int, places=5)
401
+
402
+
403
+ @integrand
404
+ def grad_decomposition(s: Sample, u: Field, v: Field):
405
+ return wp.length_sq(grad(u, s) * v(s) - D(u, s) * v(s) - wp.cross(curl(u, s), v(s)))
406
+
407
+
408
+ def test_grad_decomposition(test, device):
409
+ rng = np.random.default_rng(123)
410
+
411
+ with wp.ScopedDevice(device):
412
+ # Grid geometry
413
+ geo = fem.Grid3D(res=wp.vec3i(5))
414
+
415
+ # Domain and function spaces
416
+ domain = fem.Cells(geometry=geo)
417
+ quadrature = fem.RegularQuadrature(domain=domain, order=4)
418
+
419
+ vector_space = fem.make_polynomial_space(geo, degree=2, dtype=wp.vec3)
420
+ u = vector_space.make_field()
421
+
422
+ u.dof_values = rng.random(size=(u.dof_values.shape[0], 3))
423
+
424
+ err = fem.integrate(grad_decomposition, quadrature=quadrature, fields={"u": u, "v": u})
425
+ test.assertLess(err, 1.0e-8)
426
+
427
+
428
+ def _gen_trimesh(Nx, Ny):
429
+ x = np.linspace(0.0, 1.0, Nx + 1)
430
+ y = np.linspace(0.0, 1.0, Ny + 1)
431
+
432
+ positions = np.transpose(np.meshgrid(x, y, indexing="ij"), axes=(1, 2, 0)).reshape(-1, 2)
433
+
434
+ vidx = grid_to_tris(Nx, Ny)
435
+
436
+ return wp.array(positions, dtype=wp.vec2), wp.array(vidx, dtype=int)
437
+
438
+
439
+ def _gen_quadmesh(N):
440
+ x = np.linspace(0.0, 1.0, N + 1)
441
+ y = np.linspace(0.0, 1.0, N + 1)
442
+
443
+ positions = np.transpose(np.meshgrid(x, y, indexing="ij"), axes=(1, 2, 0)).reshape(-1, 2)
444
+
445
+ vidx = grid_to_quads(N, N)
446
+
447
+ return wp.array(positions, dtype=wp.vec2), wp.array(vidx, dtype=int)
448
+
449
+
450
+ def _gen_tetmesh(Nx, Ny, Nz):
451
+ x = np.linspace(0.0, 1.0, Nx + 1)
452
+ y = np.linspace(0.0, 1.0, Ny + 1)
453
+ z = np.linspace(0.0, 1.0, Nz + 1)
454
+
455
+ positions = np.transpose(np.meshgrid(x, y, z, indexing="ij"), axes=(1, 2, 3, 0)).reshape(-1, 3)
456
+
457
+ vidx = grid_to_tets(Nx, Ny, Nz)
458
+
459
+ return wp.array(positions, dtype=wp.vec3), wp.array(vidx, dtype=int)
460
+
461
+
462
+ def _gen_hexmesh(N):
463
+ x = np.linspace(0.0, 1.0, N + 1)
464
+ y = np.linspace(0.0, 1.0, N + 1)
465
+ z = np.linspace(0.0, 1.0, N + 1)
466
+
467
+ positions = np.transpose(np.meshgrid(x, y, z, indexing="ij"), axes=(1, 2, 3, 0)).reshape(-1, 3)
468
+
469
+ vidx = grid_to_hexes(N, N, N)
470
+
471
+ return wp.array(positions, dtype=wp.vec3), wp.array(vidx, dtype=int)
472
+
473
+
474
+ @fem.integrand(kernel_options={"enable_backward": False})
475
+ def _test_geo_cells(
476
+ s: fem.Sample,
477
+ domain: fem.Domain,
478
+ cell_measures: wp.array(dtype=float),
479
+ ):
480
+ wp.atomic_add(cell_measures, s.element_index, fem.measure(domain, s) * s.qp_weight)
481
+
482
+
483
+ @fem.integrand(kernel_options={"enable_backward": False, "max_unroll": 2})
484
+ def _test_cell_lookup(s: fem.Sample, domain: fem.Domain, cell_filter: wp.array(dtype=int)):
485
+ pos = domain(s)
486
+
487
+ s_guess = fem.lookup(domain, pos, s)
488
+ wp.expect_eq(s_guess.element_index, s.element_index)
489
+ wp.expect_near(domain(s_guess), pos, 0.001)
490
+
491
+ s_noguess = fem.lookup(domain, pos)
492
+ wp.expect_eq(s_noguess.element_index, s.element_index)
493
+ wp.expect_near(domain(s_noguess), pos, 0.001)
494
+
495
+ # Filtered lookup
496
+ max_dist = 10.0
497
+ filter_target = 1
498
+ s_filter = fem.lookup(domain, pos, max_dist, cell_filter, filter_target)
499
+ wp.expect_eq(s_filter.element_index, 0)
500
+
501
+ if s.element_index != 0:
502
+ # test closest point optimality
503
+ pos_f = domain(s_filter)
504
+ pos_f += 0.1 * (pos - pos_f)
505
+ coord_proj, _sq_dist = fem.element_closest_point(domain, s_filter.element_index, pos_f)
506
+ wp.expect_near(coord_proj, s_filter.element_coords, 0.001)
507
+
508
+ # test that extrapolated coordinates yield bak correct position
509
+ s_filter.element_coords = fem.element_coordinates(domain, s_filter.element_index, pos)
510
+ wp.expect_near(domain(s_filter), pos, 0.001)
511
+
512
+
513
+ @fem.integrand(kernel_options={"enable_backward": False, "max_unroll": 1})
514
+ def _test_geo_sides(
515
+ s: fem.Sample,
516
+ domain: fem.Domain,
517
+ ref_measure: float,
518
+ side_measures: wp.array(dtype=float),
519
+ ):
520
+ side_index = s.element_index
521
+ coords = s.element_coords
522
+
523
+ cells = fem.cells(domain)
524
+
525
+ inner_s = fem.to_inner_cell(domain, s)
526
+ outer_s = fem.to_outer_cell(domain, s)
527
+
528
+ pos_side = domain(s)
529
+ pos_inner = cells(inner_s)
530
+ pos_outer = cells(outer_s)
531
+
532
+ for k in range(type(pos_side).length):
533
+ wp.expect_near(pos_side[k], pos_inner[k], 0.0001)
534
+ wp.expect_near(pos_side[k], pos_outer[k], 0.0001)
535
+
536
+ inner_side_s = fem.to_cell_side(domain, inner_s, side_index)
537
+ outer_side_s = fem.to_cell_side(domain, outer_s, side_index)
538
+
539
+ wp.expect_near(coords, inner_side_s.element_coords, 0.0001)
540
+ wp.expect_near(coords, outer_side_s.element_coords, 0.0001)
541
+ wp.expect_near(coords, fem.element_coordinates(domain, side_index, pos_side), 0.001)
542
+
543
+ area = fem.measure(domain, s)
544
+ wp.atomic_add(side_measures, side_index, area * s.qp_weight)
545
+
546
+ F = fem.deformation_gradient(domain, s)
547
+ F_det = fem.Geometry._element_measure(F)
548
+ wp.expect_near(F_det * ref_measure, area)
549
+
550
+
551
+ @fem.integrand(kernel_options={"enable_backward": False, "max_unroll": 1})
552
+ def _test_side_normals(
553
+ s: fem.Sample,
554
+ domain: fem.Domain,
555
+ ):
556
+ # test consistency of side normal, measure, and deformation gradient
557
+ F = fem.deformation_gradient(domain, s)
558
+
559
+ nor = fem.normal(domain, s)
560
+ F_cross = fem.Geometry._element_normal(F)
561
+
562
+ for k in range(type(nor).length):
563
+ wp.expect_near(F_cross[k], nor[k], 0.0001)
564
+
565
+
566
+ def _launch_test_geometry_kernel(geo: fem.Geometry, device):
567
+ cell_measures = wp.zeros(dtype=float, device=device, shape=geo.cell_count())
568
+ cell_quadrature = fem.RegularQuadrature(fem.Cells(geo), order=2)
569
+
570
+ side_measures = wp.zeros(dtype=float, device=device, shape=geo.side_count())
571
+ side_quadrature = fem.RegularQuadrature(fem.Sides(geo), order=2)
572
+
573
+ with wp.ScopedDevice(device):
574
+ fem.interpolate(
575
+ _test_geo_cells,
576
+ quadrature=cell_quadrature,
577
+ values={"cell_measures": cell_measures},
578
+ )
579
+
580
+ cell_filter = np.zeros(geo.cell_count(), dtype=int)
581
+ cell_filter[0] = 1
582
+ cell_filter = wp.array(cell_filter, dtype=int)
583
+ fem.interpolate(_test_cell_lookup, quadrature=cell_quadrature, values={"cell_filter": cell_filter})
584
+
585
+ fem.interpolate(
586
+ _test_geo_sides,
587
+ quadrature=side_quadrature,
588
+ values={"side_measures": side_measures, "ref_measure": geo.reference_side().prototype.measure()},
589
+ )
590
+
591
+ if geo.side_normal is not None:
592
+ fem.interpolate(
593
+ _test_side_normals,
594
+ quadrature=side_quadrature,
595
+ )
596
+
597
+ return side_measures, cell_measures
598
+
599
+
600
+ def test_grid_2d(test, device):
601
+ N = 3
602
+
603
+ geo = fem.Grid2D(res=wp.vec2i(N))
604
+
605
+ test.assertEqual(geo.cell_count(), N**2)
606
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 2)
607
+ test.assertEqual(geo.side_count(), 2 * (N + 1) * N)
608
+ test.assertEqual(geo.boundary_side_count(), 4 * N)
609
+
610
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
611
+
612
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N)), tol=1.0e-4)
613
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
614
+
615
+
616
+ def test_triangle_mesh(test, device):
617
+ N = 3
618
+
619
+ with wp.ScopedDevice(device):
620
+ positions, tri_vidx = _gen_trimesh(N, N)
621
+
622
+ geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=positions, build_bvh=True)
623
+
624
+ test.assertEqual(geo.cell_count(), 2 * (N) ** 2)
625
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 2)
626
+ test.assertEqual(geo.side_count(), 2 * (N + 1) * N + (N**2))
627
+ test.assertEqual(geo.boundary_side_count(), 4 * N)
628
+
629
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
630
+
631
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 0.5 / (N**2)), tol=1.0e-4)
632
+ test.assertAlmostEqual(np.sum(side_measures.numpy()), 2 * (N + 1) + N * math.sqrt(2.0), places=4)
633
+
634
+ # 3d
635
+
636
+ positions = positions.numpy()
637
+ positions = np.hstack((positions, np.ones((positions.shape[0], 1))))
638
+ positions = wp.array(positions, device=device, dtype=wp.vec3)
639
+
640
+ geo = fem.Trimesh3D(tri_vertex_indices=tri_vidx, positions=positions, build_bvh=True)
641
+
642
+ test.assertEqual(geo.cell_count(), 2 * (N) ** 2)
643
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 2)
644
+ test.assertEqual(geo.side_count(), 2 * (N + 1) * N + (N**2))
645
+ test.assertEqual(geo.boundary_side_count(), 4 * N)
646
+
647
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
648
+
649
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 0.5 / (N**2)), tol=1.0e-4)
650
+ test.assertAlmostEqual(np.sum(side_measures.numpy()), 2 * (N + 1) + N * math.sqrt(2.0), places=4)
651
+
652
+
653
+ def test_quad_mesh(test, device):
654
+ N = 3
655
+
656
+ with wp.ScopedDevice(device):
657
+ positions, quad_vidx = _gen_quadmesh(N)
658
+
659
+ geo = fem.Quadmesh2D(quad_vertex_indices=quad_vidx, positions=positions, build_bvh=True)
660
+
661
+ test.assertEqual(geo.cell_count(), N**2)
662
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 2)
663
+ test.assertEqual(geo.side_count(), 2 * (N + 1) * N)
664
+ test.assertEqual(geo.boundary_side_count(), 4 * N)
665
+
666
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
667
+
668
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N)), tol=1.0e-4)
669
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
670
+
671
+ # 3d
672
+
673
+ positions = positions.numpy()
674
+ positions = np.hstack((positions, np.ones((positions.shape[0], 1))))
675
+ positions = wp.array(positions, device=device, dtype=wp.vec3)
676
+
677
+ geo = fem.Quadmesh3D(quad_vertex_indices=quad_vidx, positions=positions, build_bvh=True)
678
+
679
+ test.assertEqual(geo.cell_count(), N**2)
680
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 2)
681
+ test.assertEqual(geo.side_count(), 2 * (N + 1) * N)
682
+ test.assertEqual(geo.boundary_side_count(), 4 * N)
683
+
684
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
685
+
686
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N)), tol=1.0e-4)
687
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
688
+
689
+
690
+ def test_grid_3d(test, device):
691
+ N = 3
692
+
693
+ geo = fem.Grid3D(res=wp.vec3i(N))
694
+
695
+ test.assertEqual(geo.cell_count(), (N) ** 3)
696
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 3)
697
+ test.assertEqual(geo.side_count(), 3 * (N + 1) * N**2)
698
+ test.assertEqual(geo.boundary_side_count(), 6 * N * N)
699
+ test.assertEqual(geo.edge_count(), 3 * N * (N + 1) ** 2)
700
+
701
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
702
+
703
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
704
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**3)), tol=1.0e-4)
705
+
706
+
707
+ def test_tet_mesh(test, device):
708
+ N = 3
709
+
710
+ with wp.ScopedDevice(device):
711
+ positions, tet_vidx = _gen_tetmesh(N, N, N)
712
+
713
+ geo = fem.Tetmesh(tet_vertex_indices=tet_vidx, positions=positions, build_bvh=True)
714
+
715
+ test.assertEqual(geo.cell_count(), 5 * (N) ** 3)
716
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 3)
717
+ test.assertEqual(geo.side_count(), 6 * (N + 1) * N**2 + (N**3) * 4)
718
+ test.assertEqual(geo.boundary_side_count(), 12 * N * N)
719
+ test.assertEqual(geo.edge_count(), 3 * N * (N + 1) * (2 * N + 1))
720
+
721
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
722
+
723
+ test.assertAlmostEqual(np.sum(cell_measures.numpy()), 1.0, places=4)
724
+ test.assertAlmostEqual(np.sum(side_measures.numpy()), 0.5 * 6 * (N + 1) + N * 2 * math.sqrt(3.0), places=4)
725
+
726
+
727
+ def test_hex_mesh(test, device):
728
+ N = 3
729
+
730
+ with wp.ScopedDevice(device):
731
+ positions, tet_vidx = _gen_hexmesh(N)
732
+
733
+ geo = fem.Hexmesh(hex_vertex_indices=tet_vidx, positions=positions, build_bvh=True)
734
+
735
+ test.assertEqual(geo.cell_count(), (N) ** 3)
736
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 3)
737
+ test.assertEqual(geo.side_count(), 3 * (N + 1) * N**2)
738
+ test.assertEqual(geo.boundary_side_count(), 6 * N * N)
739
+ test.assertEqual(geo.edge_count(), 3 * N * (N + 1) ** 2)
740
+
741
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
742
+
743
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
744
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**3)), tol=1.0e-4)
745
+
746
+
747
+ def test_nanogrid(test, device):
748
+ N = 8
749
+
750
+ points = wp.array([[0.5, 0.5, 0.5]], dtype=float, device=device)
751
+ volume = wp.Volume.allocate_by_tiles(
752
+ tile_points=points, voxel_size=1.0 / N, translation=(0.0, 0.0, 0.0), bg_value=None, device=device
753
+ )
754
+
755
+ geo = fem.Nanogrid(volume)
756
+
757
+ test.assertEqual(geo.cell_count(), (N) ** 3)
758
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 3)
759
+ test.assertEqual(geo.side_count(), 3 * (N + 1) * N**2)
760
+ test.assertEqual(geo.boundary_side_count(), 6 * N * N)
761
+ test.assertEqual(geo.edge_count(), 3 * N * (N + 1) ** 2)
762
+
763
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
764
+
765
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
766
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**3)), tol=1.0e-4)
767
+
768
+
769
+ @wp.func
770
+ def _refinement_field(x: wp.vec3):
771
+ return 4.0 * (wp.length(x) - 0.5)
772
+
773
+
774
+ def test_adaptive_nanogrid(test, device):
775
+ # 3 res-1 voxels, 8 res-0 voxels
776
+
777
+ if platform.system() == "Windows" or (device.is_cuda and wp._src.context.runtime.toolkit_version[0] == 11):
778
+ test.skipTest("Skipping test due to NVRTC bug on CUDA 11 and Windows")
779
+
780
+ res0 = wp.array(
781
+ [
782
+ [2, 2, 0],
783
+ [2, 3, 0],
784
+ [3, 2, 0],
785
+ [3, 3, 0],
786
+ [2, 2, 1],
787
+ [2, 3, 1],
788
+ [3, 2, 1],
789
+ [3, 3, 1],
790
+ ],
791
+ dtype=int,
792
+ device=device,
793
+ )
794
+ res1 = wp.array(
795
+ [
796
+ [0, 0, 0],
797
+ [0, 1, 0],
798
+ [1, 0, 0],
799
+ [1, 1, 0],
800
+ ],
801
+ dtype=int,
802
+ device=device,
803
+ )
804
+ grid0 = wp.Volume.allocate_by_voxels(res0, 0.5, device=device)
805
+ grid1 = wp.Volume.allocate_by_voxels(res1, 1.0, device=device)
806
+ geo = fem.adaptive_nanogrid_from_hierarchy([grid0, grid1])
807
+
808
+ test.assertEqual(geo.cell_count(), 3 + 8)
809
+ test.assertEqual(geo.vertex_count(), 2 * 9 + 27 - 8)
810
+ test.assertEqual(geo.side_count(), 2 * 4 + 6 * 2 + (3 * (2 + 1) * 2**2 - 6))
811
+ test.assertEqual(geo.boundary_side_count(), 2 * 4 + 4 * 2 + (4 * 4 - 4))
812
+ # test.assertEqual(geo.edge_count(), 6 * 4 + 9 + (3 * 2 * (2 + 1) ** 2 - 12))
813
+ test.assertEqual(geo.stacked_face_count(), geo.side_count() + 2)
814
+ test.assertEqual(geo.stacked_edge_count(), 6 * 4 + 9 + (3 * 2 * (2 + 1) ** 2 - 12) + 7)
815
+
816
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
817
+
818
+ test.assertAlmostEqual(np.sum(cell_measures.numpy()), 4.0, places=4)
819
+ test.assertAlmostEqual(np.sum(side_measures.numpy()), 20 + 3.0, places=4)
820
+
821
+ # Test with non-graded geometry
822
+ ref_field = fem.ImplicitField(fem.Cells(geo), func=_refinement_field)
823
+ non_graded_geo = fem.adaptive_nanogrid_from_field(grid1, level_count=3, refinement_field=ref_field)
824
+ _launch_test_geometry_kernel(geo, device)
825
+
826
+ # Test automatic grading
827
+ graded_geo = fem.adaptive_nanogrid_from_field(grid1, level_count=3, refinement_field=ref_field, grading="face")
828
+ test.assertEqual(non_graded_geo.cell_count() + 7, graded_geo.cell_count())
829
+
830
+
831
+ @integrand
832
+ def _rigid_deformation_field(s: Sample, domain: Domain, translation: wp.vec3, rotation: wp.vec3, scale: float):
833
+ q = wp.quat_from_axis_angle(wp.normalize(rotation), wp.length(rotation))
834
+ return translation + scale * wp.quat_rotate(q, domain(s)) - domain(s)
835
+
836
+
837
+ def test_deformed_geometry(test, device):
838
+ N = 3
839
+
840
+ translation = [1.0, 2.0, 3.0]
841
+ rotation = [0.0, math.pi / 4.0, 0.0]
842
+ scale = 2.0
843
+
844
+ with wp.ScopedDevice(device):
845
+ positions, tet_vidx = _gen_tetmesh(N, N, N)
846
+
847
+ geo = fem.Tetmesh(tet_vertex_indices=tet_vidx, positions=positions)
848
+
849
+ vector_space = fem.make_polynomial_space(geo, dtype=wp.vec3, degree=2)
850
+ pos_field = vector_space.make_field()
851
+ fem.interpolate(
852
+ _rigid_deformation_field,
853
+ dest=pos_field,
854
+ values={"translation": translation, "rotation": rotation, "scale": scale},
855
+ )
856
+
857
+ deformed_geo = pos_field.make_deformed_geometry()
858
+
859
+ # rigidly-deformed geometry
860
+
861
+ test.assertEqual(geo.cell_count(), 5 * (N) ** 3)
862
+ test.assertEqual(geo.vertex_count(), (N + 1) ** 3)
863
+ test.assertEqual(geo.side_count(), 6 * (N + 1) * N**2 + (N**3) * 4)
864
+ test.assertEqual(geo.boundary_side_count(), 12 * N * N)
865
+
866
+ deformed_geo.build_bvh()
867
+ side_measures, cell_measures = _launch_test_geometry_kernel(deformed_geo, device)
868
+
869
+ test.assertAlmostEqual(
870
+ np.sum(cell_measures.numpy()), scale**3, places=4, msg=f"cell_measures = {cell_measures.numpy()}"
871
+ )
872
+ test.assertAlmostEqual(
873
+ np.sum(side_measures.numpy()), scale**2 * (0.5 * 6 * (N + 1) + N * 2 * math.sqrt(3.0)), places=4
874
+ )
875
+
876
+ @fem.cache.dynamic_kernel(suffix=deformed_geo.name, kernel_options={"enable_backward": False})
877
+ def _test_deformed_geometry_normal(
878
+ geo_index_arg: geo.SideIndexArg, geo_arg: geo.SideArg, def_arg: deformed_geo.SideArg, rotation: wp.vec3
879
+ ):
880
+ i = wp.tid()
881
+ side_index = deformed_geo.boundary_side_index(geo_index_arg, i)
882
+
883
+ s = make_free_sample(side_index, Coords(0.5, 0.5, 0.0))
884
+ geo_n = geo.side_normal(geo_arg, s)
885
+ def_n = deformed_geo.side_normal(def_arg, s)
886
+
887
+ q = wp.quat_from_axis_angle(wp.normalize(rotation), wp.length(rotation))
888
+ wp.expect_near(wp.quat_rotate(q, geo_n), def_n, 0.001)
889
+
890
+ wp.launch(
891
+ _test_deformed_geometry_normal,
892
+ dim=geo.boundary_side_count(),
893
+ inputs=[
894
+ geo.side_index_arg_value(wp.get_device()),
895
+ geo.side_arg_value(wp.get_device()),
896
+ deformed_geo.side_arg_value(wp.get_device()),
897
+ rotation,
898
+ ],
899
+ )
900
+
901
+
902
+ def test_deformed_geometry_codimensional(test, device):
903
+ N = 3
904
+
905
+ translation = [1.0, 2.0, 3.0]
906
+ rotation = [0.0, math.pi / 4.0, 0.0]
907
+ scale = 2.0
908
+
909
+ with wp.ScopedDevice(device):
910
+ # Test with Trimesh3d (different space and cell dimensions)
911
+ positions, tri_vidx = _gen_trimesh(N, N)
912
+ positions = positions.numpy()
913
+ positions = np.hstack((positions, np.ones((positions.shape[0], 1))))
914
+ positions = wp.array(positions, device=device, dtype=wp.vec3)
915
+
916
+ geo = fem.Trimesh3D(tri_vertex_indices=tri_vidx, positions=positions)
917
+
918
+ vector_space = fem.make_polynomial_space(geo, dtype=wp.vec3, degree=1)
919
+ pos_field = vector_space.make_field()
920
+ fem.interpolate(
921
+ _rigid_deformation_field,
922
+ dest=pos_field,
923
+ values={"translation": translation, "rotation": rotation, "scale": scale},
924
+ )
925
+
926
+ deformed_geo = pos_field.make_deformed_geometry()
927
+
928
+ @fem.cache.dynamic_kernel(suffix=deformed_geo.name, kernel_options={"enable_backward": False})
929
+ def _test_deformed_geometry_normal_codimensional(
930
+ geo_arg: geo.CellArg, def_arg: deformed_geo.CellArg, rotation: wp.vec3
931
+ ):
932
+ i = wp.tid()
933
+
934
+ s = make_free_sample(i, Coords(0.5, 0.5, 0.0))
935
+ geo_n = geo.cell_normal(geo_arg, s)
936
+ def_n = deformed_geo.cell_normal(def_arg, s)
937
+
938
+ q = wp.quat_from_axis_angle(wp.normalize(rotation), wp.length(rotation))
939
+ wp.expect_near(wp.quat_rotate(q, geo_n), def_n, 0.001)
940
+
941
+ wp.launch(
942
+ _test_deformed_geometry_normal_codimensional,
943
+ dim=geo.cell_count(),
944
+ inputs=[
945
+ geo.cell_arg_value(wp.get_device()),
946
+ deformed_geo.cell_arg_value(wp.get_device()),
947
+ rotation,
948
+ ],
949
+ )
950
+
951
+ wp.synchronize()
952
+
953
+
954
+ @wp.kernel
955
+ def _test_closest_point_on_tri_kernel(
956
+ e0: wp.vec2,
957
+ e1: wp.vec2,
958
+ points: wp.array(dtype=wp.vec2),
959
+ sq_dist: wp.array(dtype=float),
960
+ coords: wp.array(dtype=Coords),
961
+ ):
962
+ i = wp.tid()
963
+ d2, c = project_on_tri_at_origin(points[i], e0, e1)
964
+ sq_dist[i] = d2
965
+ coords[i] = c
966
+
967
+
968
+ @wp.kernel
969
+ def _test_closest_point_on_tet_kernel(
970
+ e0: wp.vec3,
971
+ e1: wp.vec3,
972
+ e2: wp.vec3,
973
+ points: wp.array(dtype=wp.vec3),
974
+ sq_dist: wp.array(dtype=float),
975
+ coords: wp.array(dtype=Coords),
976
+ ):
977
+ i = wp.tid()
978
+ d2, c = project_on_tet_at_origin(points[i], e0, e1, e2)
979
+ sq_dist[i] = d2
980
+ coords[i] = c
981
+
982
+
983
+ def test_closest_point_queries(test, device):
984
+ # Test some simple lookup queries
985
+ e0 = wp.vec2(2.0, 0.0)
986
+ e1 = wp.vec2(0.0, 2.0)
987
+
988
+ points = wp.array(
989
+ (
990
+ [-1.0, -1.0],
991
+ [0.5, 0.5],
992
+ [1.0, 1.0],
993
+ [2.0, 2.0],
994
+ ),
995
+ dtype=wp.vec2,
996
+ device=device,
997
+ )
998
+ expected_sq_dist = np.array([2.0, 0.0, 0.0, 2.0])
999
+ expected_coords = np.array([[1.0, 0.0, 0.0], [0.5, 0.25, 0.25], [0.0, 0.5, 0.5], [0.0, 0.5, 0.5]])
1000
+
1001
+ sq_dist = wp.empty(shape=points.shape, dtype=float, device=device)
1002
+ coords = wp.empty(shape=points.shape, dtype=Coords, device=device)
1003
+ wp.launch(
1004
+ _test_closest_point_on_tri_kernel, dim=points.shape, device=device, inputs=[e0, e1, points, sq_dist, coords]
1005
+ )
1006
+
1007
+ assert_np_equal(coords.numpy(), expected_coords)
1008
+ assert_np_equal(sq_dist.numpy(), expected_sq_dist)
1009
+
1010
+ # Tet
1011
+
1012
+ e0 = wp.vec3(3.0, 0.0, 0.0)
1013
+ e1 = wp.vec3(0.0, 3.0, 0.0)
1014
+ e2 = wp.vec3(0.0, 0.0, 3.0)
1015
+
1016
+ points = wp.array(
1017
+ (
1018
+ [-1.0, -1.0, -1.0],
1019
+ [0.5, 0.5, 0.5],
1020
+ [1.0, 1.0, 1.0],
1021
+ [2.0, 2.0, 2.0],
1022
+ ),
1023
+ dtype=wp.vec3,
1024
+ device=device,
1025
+ )
1026
+ expected_sq_dist = np.array([3.0, 0.0, 0.0, 3.0])
1027
+ expected_coords = np.array(
1028
+ [
1029
+ [0.0, 0.0, 0.0],
1030
+ [1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0],
1031
+ [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0],
1032
+ [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0],
1033
+ ]
1034
+ )
1035
+
1036
+ sq_dist = wp.empty(shape=points.shape, dtype=float, device=device)
1037
+ coords = wp.empty(shape=points.shape, dtype=Coords, device=device)
1038
+ wp.launch(
1039
+ _test_closest_point_on_tet_kernel, dim=points.shape, device=device, inputs=[e0, e1, e2, points, sq_dist, coords]
1040
+ )
1041
+
1042
+ assert_np_equal(coords.numpy(), expected_coords, tol=1.0e-4)
1043
+ assert_np_equal(sq_dist.numpy(), expected_sq_dist, tol=1.0e-4)
1044
+
1045
+
1046
+ def test_regular_quadrature(test, device):
1047
+ from warp._src.fem.geometry.element import LinearEdge, Polynomial, Triangle
1048
+
1049
+ for family in Polynomial:
1050
+ # test integrating monomials
1051
+ for degree in range(8):
1052
+ coords, weights = LinearEdge().instantiate_quadrature(degree, family=family)
1053
+ res = sum(w * pow(c[0], degree) for w, c in zip(weights, coords))
1054
+ ref = 1.0 / (degree + 1)
1055
+
1056
+ test.assertAlmostEqual(ref, res, places=4)
1057
+
1058
+ # test integrating y^k1 (1 - x)^k2 on triangle using transformation to square
1059
+ for x_degree in range(4):
1060
+ for y_degree in range(4):
1061
+ coords, weights = Triangle().instantiate_quadrature(x_degree + y_degree, family=family)
1062
+ res = 0.5 * sum(w * pow(1.0 - c[1], x_degree) * pow(c[2], y_degree) for w, c in zip(weights, coords))
1063
+
1064
+ ref = 1.0 / ((x_degree + y_degree + 2) * (y_degree + 1))
1065
+ # print(x_degree, y_degree, family, len(coords), res, ref)
1066
+ test.assertAlmostEqual(ref, res, places=4)
1067
+
1068
+ # test integrating y^k1 (1 - x)^k2 on triangle using direct formulas
1069
+ for x_degree in range(5):
1070
+ for y_degree in range(5):
1071
+ coords, weights = Triangle().instantiate_quadrature(x_degree + y_degree, family=None)
1072
+ res = 0.5 * sum(w * pow(1.0 - c[1], x_degree) * pow(c[2], y_degree) for w, c in zip(weights, coords))
1073
+
1074
+ ref = 1.0 / ((x_degree + y_degree + 2) * (y_degree + 1))
1075
+ test.assertAlmostEqual(ref, res, places=4)
1076
+
1077
+
1078
+ def test_dof_mapper(test, device):
1079
+ matrix_types = [wp.mat22, wp.mat33]
1080
+
1081
+ # Symmetric mapper
1082
+ for mapping in fem.SymmetricTensorMapper.Mapping:
1083
+ for dtype in matrix_types:
1084
+ mapper = fem.SymmetricTensorMapper(dtype, mapping=mapping)
1085
+ dof_dtype = mapper.dof_dtype
1086
+
1087
+ for k in range(dof_dtype._length_):
1088
+ elem = np.array(dof_dtype(0.0))
1089
+ elem[k] = 1.0
1090
+ dof_vec = dof_dtype(elem)
1091
+
1092
+ mat = mapper.dof_to_value(dof_vec)
1093
+ dof_round_trip = mapper.value_to_dof(mat)
1094
+
1095
+ # Check that value_to_dof(dof_to_value) is idempotent
1096
+ assert_np_equal(np.array(dof_round_trip), np.array(dof_vec))
1097
+
1098
+ # Check that value is unitary for Frobenius norm 0.5 * |tau:tau|
1099
+ frob_norm2 = 0.5 * wp.ddot(mat, mat)
1100
+ test.assertAlmostEqual(frob_norm2, 1.0, places=6)
1101
+
1102
+ # Skew-symmetric mapper
1103
+ for dtype in matrix_types:
1104
+ mapper = fem.SkewSymmetricTensorMapper(dtype)
1105
+ dof_dtype = mapper.dof_dtype
1106
+
1107
+ if hasattr(dof_dtype, "_length_"):
1108
+ for k in range(dof_dtype._length_):
1109
+ elem = np.array(dof_dtype(0.0))
1110
+ elem[k] = 1.0
1111
+ dof_vec = dof_dtype(elem)
1112
+
1113
+ mat = mapper.dof_to_value(dof_vec)
1114
+ dof_round_trip = mapper.value_to_dof(mat)
1115
+
1116
+ # Check that value_to_dof(dof_to_value) is idempotent
1117
+ assert_np_equal(np.array(dof_round_trip), np.array(dof_vec))
1118
+
1119
+ # Check that value is unitary for Frobenius norm 0.5 * |tau:tau|
1120
+ frob_norm2 = 0.5 * wp.ddot(mat, mat)
1121
+ test.assertAlmostEqual(frob_norm2, 1.0, places=6)
1122
+ else:
1123
+ dof_val = 1.0
1124
+
1125
+ mat = mapper.dof_to_value(dof_val)
1126
+ dof_round_trip = mapper.value_to_dof(mat)
1127
+
1128
+ test.assertAlmostEqual(dof_round_trip, dof_val)
1129
+
1130
+ # Check that value is unitary for Frobenius norm 0.5 * |tau:tau|
1131
+ frob_norm2 = 0.5 * wp.ddot(mat, mat)
1132
+ test.assertAlmostEqual(frob_norm2, 1.0, places=6)
1133
+
1134
+
1135
+ @wp.func
1136
+ def _expect_near(a: Any, b: Any, tol: float):
1137
+ wp.expect_near(a, b, tol)
1138
+
1139
+
1140
+ @wp.func
1141
+ def _expect_near(a: wp.vec2, b: wp.vec2, tol: float):
1142
+ for k in range(2):
1143
+ wp.expect_near(a[k], b[k], tol)
1144
+
1145
+
1146
+ def test_shape_function_weight(test, shape: shape.ShapeFunction, coord_sampler, CENTER_COORDS):
1147
+ NODE_COUNT = shape.NODES_PER_ELEMENT
1148
+ weight_fn = shape.make_element_inner_weight()
1149
+ node_coords_fn = shape.make_node_coords_in_element()
1150
+
1151
+ # Weight at node should be 1
1152
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
1153
+ def node_unity_test():
1154
+ n = wp.tid()
1155
+ node_w = weight_fn(node_coords_fn(n), n)
1156
+ wp.expect_near(node_w, 1.0, 1e-5)
1157
+
1158
+ wp.launch(node_unity_test, dim=NODE_COUNT, inputs=[])
1159
+
1160
+ # Sum of node quadrature weights should be one (order 0)
1161
+ # Sum of weighted quadrature coords should be element center (order 1)
1162
+ node_quadrature_weight_fn = shape.make_node_quadrature_weight()
1163
+
1164
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
1165
+ def node_quadrature_unity_test():
1166
+ sum_node_qp = float(0.0)
1167
+ sum_node_qp_coords = Coords(0.0)
1168
+
1169
+ for n in range(NODE_COUNT):
1170
+ w = node_quadrature_weight_fn(n)
1171
+ sum_node_qp += w
1172
+ sum_node_qp_coords += w * node_coords_fn(n)
1173
+
1174
+ wp.expect_near(sum_node_qp, 1.0, 0.0001)
1175
+ wp.expect_near(sum_node_qp_coords, CENTER_COORDS, 0.0001)
1176
+
1177
+ wp.launch(node_quadrature_unity_test, dim=1, inputs=[])
1178
+
1179
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
1180
+ def partition_of_unity_test():
1181
+ rng_state = wp.rand_init(4321, wp.tid())
1182
+ coords = coord_sampler(rng_state)
1183
+
1184
+ # sum of node weights anywhere should be 1.0
1185
+ w_sum = type(weight_fn(coords, 0))(0.0)
1186
+ for n in range(NODE_COUNT):
1187
+ w_sum += weight_fn(coords, n)
1188
+
1189
+ _expect_near(wp.abs(w_sum), type(w_sum)(1.0), 0.0001)
1190
+
1191
+ n_samples = 100
1192
+ wp.launch(partition_of_unity_test, dim=n_samples, inputs=[])
1193
+
1194
+
1195
+ def test_shape_function_trace(test, shape: shape.ShapeFunction, CENTER_COORDS):
1196
+ NODE_COUNT = shape.NODES_PER_ELEMENT
1197
+ node_coords_fn = shape.make_node_coords_in_element()
1198
+
1199
+ # Sum of node quadrature weights should be one (order 0)
1200
+ # Sum of weighted quadrature coords should be element center (order 1)
1201
+ trace_node_quadrature_weight_fn = shape.make_trace_node_quadrature_weight()
1202
+
1203
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
1204
+ def trace_node_quadrature_unity_test():
1205
+ sum_node_qp = float(0.0)
1206
+ sum_node_qp_coords = Coords(0.0)
1207
+
1208
+ for n in range(NODE_COUNT):
1209
+ coords = node_coords_fn(n)
1210
+
1211
+ if wp.abs(coords[0]) < 1.0e-6:
1212
+ w = trace_node_quadrature_weight_fn(n)
1213
+ sum_node_qp += w
1214
+ sum_node_qp_coords += w * node_coords_fn(n)
1215
+
1216
+ wp.expect_near(sum_node_qp, 1.0, 0.0001)
1217
+ wp.expect_near(sum_node_qp_coords, CENTER_COORDS, 0.0001)
1218
+
1219
+ wp.launch(trace_node_quadrature_unity_test, dim=1, inputs=[])
1220
+
1221
+
1222
+ def test_shape_function_gradient(
1223
+ test,
1224
+ shape: shape.ShapeFunction,
1225
+ coord_sampler,
1226
+ coord_delta_sampler,
1227
+ pure_curl: bool = False,
1228
+ pure_spherical: bool = False,
1229
+ ):
1230
+ weight_fn = shape.make_element_inner_weight()
1231
+ weight_gradient_fn = shape.make_element_inner_weight_gradient()
1232
+
1233
+ @wp.func
1234
+ def scalar_delta(avg_grad: Any, param_delta: Any):
1235
+ return wp.dot(avg_grad, param_delta)
1236
+
1237
+ @wp.func
1238
+ def vector_delta(avg_grad: Any, param_delta: Any):
1239
+ return avg_grad * param_delta
1240
+
1241
+ grad_delta_fn = scalar_delta if shape.value == shape.Value.Scalar else vector_delta
1242
+
1243
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
1244
+ def finite_difference_test():
1245
+ i, n = wp.tid()
1246
+ rng_state = wp.rand_init(1234, i)
1247
+
1248
+ coords = coord_sampler(rng_state)
1249
+
1250
+ epsilon = 0.003
1251
+ param_delta, coords_delta = coord_delta_sampler(epsilon, rng_state)
1252
+
1253
+ w_p = weight_fn(coords + coords_delta, n)
1254
+ w_m = weight_fn(coords - coords_delta, n)
1255
+
1256
+ gp = weight_gradient_fn(coords + coords_delta, n)
1257
+ gm = weight_gradient_fn(coords - coords_delta, n)
1258
+
1259
+ # 2nd-order finite-difference test
1260
+ # See Schroeder 2019, Practical course on computing derivatives in code
1261
+ delta_ref = w_p - w_m
1262
+ delta_est = grad_delta_fn(gp + gm, param_delta)
1263
+ _expect_near(delta_ref, delta_est, 0.0001)
1264
+
1265
+ if wp.static(pure_curl):
1266
+ wp.expect_near(wp.ddot(symmetric_part(gp), symmetric_part(gp)), gp.dtype(0.0))
1267
+
1268
+ if wp.static(pure_spherical):
1269
+ deviatoric_part = gp - spherical_part(gp)
1270
+ wp.expect_near(wp.ddot(deviatoric_part, deviatoric_part), gp.dtype(0.0))
1271
+
1272
+ n_samples = 100
1273
+ wp.launch(finite_difference_test, dim=(n_samples, shape.NODES_PER_ELEMENT), inputs=[])
1274
+
1275
+
1276
+ def test_square_shape_functions(test, device):
1277
+ SQUARE_CENTER_COORDS = wp.constant(Coords(0.5, 0.5, 0.0))
1278
+ SQUARE_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 0.5, 0.0))
1279
+
1280
+ @wp.func
1281
+ def square_coord_sampler(state: wp.uint32):
1282
+ return Coords(wp.randf(state), wp.randf(state), 0.0)
1283
+
1284
+ @wp.func
1285
+ def square_coord_delta_sampler(epsilon: float, state: wp.uint32):
1286
+ param_delta = wp.normalize(wp.vec2(wp.randf(state), wp.randf(state))) * epsilon
1287
+ return param_delta, Coords(param_delta[0], param_delta[1], 0.0)
1288
+
1289
+ Q_1 = shape.SquareBipolynomialShapeFunctions(degree=1, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1290
+ Q_2 = shape.SquareBipolynomialShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1291
+ Q_3 = shape.SquareBipolynomialShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1292
+
1293
+ test_shape_function_weight(test, Q_1, square_coord_sampler, SQUARE_CENTER_COORDS)
1294
+ test_shape_function_weight(test, Q_2, square_coord_sampler, SQUARE_CENTER_COORDS)
1295
+ test_shape_function_weight(test, Q_3, square_coord_sampler, SQUARE_CENTER_COORDS)
1296
+ test_shape_function_trace(test, Q_1, SQUARE_SIDE_CENTER_COORDS)
1297
+ test_shape_function_trace(test, Q_2, SQUARE_SIDE_CENTER_COORDS)
1298
+ test_shape_function_trace(test, Q_3, SQUARE_SIDE_CENTER_COORDS)
1299
+ test_shape_function_gradient(test, Q_1, square_coord_sampler, square_coord_delta_sampler)
1300
+ test_shape_function_gradient(test, Q_2, square_coord_sampler, square_coord_delta_sampler)
1301
+ test_shape_function_gradient(test, Q_3, square_coord_sampler, square_coord_delta_sampler)
1302
+
1303
+ Q_1 = shape.SquareBipolynomialShapeFunctions(degree=1, family=fem.Polynomial.GAUSS_LEGENDRE)
1304
+ Q_2 = shape.SquareBipolynomialShapeFunctions(degree=2, family=fem.Polynomial.GAUSS_LEGENDRE)
1305
+ Q_3 = shape.SquareBipolynomialShapeFunctions(degree=3, family=fem.Polynomial.GAUSS_LEGENDRE)
1306
+
1307
+ test_shape_function_weight(test, Q_1, square_coord_sampler, SQUARE_CENTER_COORDS)
1308
+ test_shape_function_weight(test, Q_2, square_coord_sampler, SQUARE_CENTER_COORDS)
1309
+ test_shape_function_weight(test, Q_3, square_coord_sampler, SQUARE_CENTER_COORDS)
1310
+ test_shape_function_gradient(test, Q_1, square_coord_sampler, square_coord_delta_sampler)
1311
+ test_shape_function_gradient(test, Q_2, square_coord_sampler, square_coord_delta_sampler)
1312
+ test_shape_function_gradient(test, Q_3, square_coord_sampler, square_coord_delta_sampler)
1313
+
1314
+ S_2 = shape.SquareSerendipityShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1315
+ S_3 = shape.SquareSerendipityShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1316
+
1317
+ test_shape_function_weight(test, S_2, square_coord_sampler, SQUARE_CENTER_COORDS)
1318
+ test_shape_function_weight(test, S_3, square_coord_sampler, SQUARE_CENTER_COORDS)
1319
+ test_shape_function_trace(test, S_2, SQUARE_SIDE_CENTER_COORDS)
1320
+ test_shape_function_trace(test, S_3, SQUARE_SIDE_CENTER_COORDS)
1321
+ test_shape_function_gradient(test, S_2, square_coord_sampler, square_coord_delta_sampler)
1322
+ test_shape_function_gradient(test, S_3, square_coord_sampler, square_coord_delta_sampler)
1323
+
1324
+ P_c1 = shape.SquareNonConformingPolynomialShapeFunctions(degree=1)
1325
+ P_c2 = shape.SquareNonConformingPolynomialShapeFunctions(degree=2)
1326
+ P_c3 = shape.SquareNonConformingPolynomialShapeFunctions(degree=3)
1327
+
1328
+ test_shape_function_weight(test, P_c1, square_coord_sampler, SQUARE_CENTER_COORDS)
1329
+ test_shape_function_weight(test, P_c2, square_coord_sampler, SQUARE_CENTER_COORDS)
1330
+ test_shape_function_weight(test, P_c3, square_coord_sampler, SQUARE_CENTER_COORDS)
1331
+ test_shape_function_gradient(test, P_c1, square_coord_sampler, square_coord_delta_sampler)
1332
+ test_shape_function_gradient(test, P_c2, square_coord_sampler, square_coord_delta_sampler)
1333
+ test_shape_function_gradient(test, P_c3, square_coord_sampler, square_coord_delta_sampler)
1334
+
1335
+ N1_1 = shape.SquareNedelecFirstKindShapeFunctions(degree=1)
1336
+ test_shape_function_gradient(test, N1_1, square_coord_sampler, square_coord_delta_sampler)
1337
+ RT_1 = shape.SquareRaviartThomasShapeFunctions(degree=1)
1338
+ test_shape_function_gradient(test, RT_1, square_coord_sampler, square_coord_delta_sampler)
1339
+
1340
+ wp.synchronize()
1341
+
1342
+
1343
+ def test_cube_shape_functions(test, device):
1344
+ CUBE_CENTER_COORDS = wp.constant(Coords(0.5, 0.5, 0.5))
1345
+ CUBE_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 0.5, 0.5))
1346
+
1347
+ @wp.func
1348
+ def cube_coord_sampler(state: wp.uint32):
1349
+ return Coords(wp.randf(state), wp.randf(state), wp.randf(state))
1350
+
1351
+ @wp.func
1352
+ def cube_coord_delta_sampler(epsilon: float, state: wp.uint32):
1353
+ param_delta = wp.normalize(wp.vec3(wp.randf(state), wp.randf(state), wp.randf(state))) * epsilon
1354
+ return param_delta, param_delta
1355
+
1356
+ Q_1 = shape.CubeTripolynomialShapeFunctions(degree=1, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1357
+ Q_2 = shape.CubeTripolynomialShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1358
+ Q_3 = shape.CubeTripolynomialShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1359
+
1360
+ test_shape_function_weight(test, Q_1, cube_coord_sampler, CUBE_CENTER_COORDS)
1361
+ test_shape_function_weight(test, Q_2, cube_coord_sampler, CUBE_CENTER_COORDS)
1362
+ test_shape_function_weight(test, Q_3, cube_coord_sampler, CUBE_CENTER_COORDS)
1363
+ test_shape_function_trace(test, Q_1, CUBE_SIDE_CENTER_COORDS)
1364
+ test_shape_function_trace(test, Q_2, CUBE_SIDE_CENTER_COORDS)
1365
+ test_shape_function_trace(test, Q_3, CUBE_SIDE_CENTER_COORDS)
1366
+ test_shape_function_gradient(test, Q_1, cube_coord_sampler, cube_coord_delta_sampler)
1367
+ test_shape_function_gradient(test, Q_2, cube_coord_sampler, cube_coord_delta_sampler)
1368
+ test_shape_function_gradient(test, Q_3, cube_coord_sampler, cube_coord_delta_sampler)
1369
+
1370
+ Q_1 = shape.CubeTripolynomialShapeFunctions(degree=1, family=fem.Polynomial.GAUSS_LEGENDRE)
1371
+ Q_2 = shape.CubeTripolynomialShapeFunctions(degree=2, family=fem.Polynomial.GAUSS_LEGENDRE)
1372
+ Q_3 = shape.CubeTripolynomialShapeFunctions(degree=3, family=fem.Polynomial.GAUSS_LEGENDRE)
1373
+
1374
+ test_shape_function_weight(test, Q_1, cube_coord_sampler, CUBE_CENTER_COORDS)
1375
+ test_shape_function_weight(test, Q_2, cube_coord_sampler, CUBE_CENTER_COORDS)
1376
+ test_shape_function_weight(test, Q_3, cube_coord_sampler, CUBE_CENTER_COORDS)
1377
+ test_shape_function_gradient(test, Q_1, cube_coord_sampler, cube_coord_delta_sampler)
1378
+ test_shape_function_gradient(test, Q_2, cube_coord_sampler, cube_coord_delta_sampler)
1379
+ test_shape_function_gradient(test, Q_3, cube_coord_sampler, cube_coord_delta_sampler)
1380
+
1381
+ S_2 = shape.CubeSerendipityShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1382
+ S_3 = shape.CubeSerendipityShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1383
+
1384
+ test_shape_function_weight(test, S_2, cube_coord_sampler, CUBE_CENTER_COORDS)
1385
+ test_shape_function_weight(test, S_3, cube_coord_sampler, CUBE_CENTER_COORDS)
1386
+ test_shape_function_trace(test, S_2, CUBE_SIDE_CENTER_COORDS)
1387
+ test_shape_function_trace(test, S_3, CUBE_SIDE_CENTER_COORDS)
1388
+ test_shape_function_gradient(test, S_2, cube_coord_sampler, cube_coord_delta_sampler)
1389
+ test_shape_function_gradient(test, S_3, cube_coord_sampler, cube_coord_delta_sampler)
1390
+
1391
+ P_c1 = shape.CubeNonConformingPolynomialShapeFunctions(degree=1)
1392
+ P_c2 = shape.CubeNonConformingPolynomialShapeFunctions(degree=2)
1393
+ P_c3 = shape.CubeNonConformingPolynomialShapeFunctions(degree=3)
1394
+
1395
+ test_shape_function_weight(test, P_c1, cube_coord_sampler, CUBE_CENTER_COORDS)
1396
+ test_shape_function_weight(test, P_c2, cube_coord_sampler, CUBE_CENTER_COORDS)
1397
+ test_shape_function_weight(test, P_c3, cube_coord_sampler, CUBE_CENTER_COORDS)
1398
+ test_shape_function_gradient(test, P_c1, cube_coord_sampler, cube_coord_delta_sampler)
1399
+ test_shape_function_gradient(test, P_c2, cube_coord_sampler, cube_coord_delta_sampler)
1400
+ test_shape_function_gradient(test, P_c3, cube_coord_sampler, cube_coord_delta_sampler)
1401
+
1402
+ N1_1 = shape.CubeNedelecFirstKindShapeFunctions(degree=1)
1403
+ test_shape_function_gradient(test, N1_1, cube_coord_sampler, cube_coord_delta_sampler)
1404
+ RT_1 = shape.CubeRaviartThomasShapeFunctions(degree=1)
1405
+ test_shape_function_gradient(test, RT_1, cube_coord_sampler, cube_coord_delta_sampler)
1406
+
1407
+ wp.synchronize()
1408
+
1409
+
1410
+ def test_tri_shape_functions(test, device):
1411
+ TRI_CENTER_COORDS = wp.constant(Coords(1 / 3.0, 1 / 3.0, 1 / 3.0))
1412
+ TRI_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 0.5, 0.5))
1413
+
1414
+ @wp.func
1415
+ def tri_coord_sampler(state: wp.uint32):
1416
+ a = wp.randf(state)
1417
+ b = wp.randf(state)
1418
+ return Coords(1.0 - a - b, a, b)
1419
+
1420
+ @wp.func
1421
+ def tri_coord_delta_sampler(epsilon: float, state: wp.uint32):
1422
+ param_delta = wp.normalize(wp.vec2(wp.randf(state), wp.randf(state))) * epsilon
1423
+ a = param_delta[0]
1424
+ b = param_delta[1]
1425
+ return param_delta, Coords(-a - b, a, b)
1426
+
1427
+ P_1 = shape.TrianglePolynomialShapeFunctions(degree=1)
1428
+ P_2 = shape.TrianglePolynomialShapeFunctions(degree=2)
1429
+ P_3 = shape.TrianglePolynomialShapeFunctions(degree=3)
1430
+
1431
+ test_shape_function_weight(test, P_1, tri_coord_sampler, TRI_CENTER_COORDS)
1432
+ test_shape_function_weight(test, P_2, tri_coord_sampler, TRI_CENTER_COORDS)
1433
+ test_shape_function_weight(test, P_3, tri_coord_sampler, TRI_CENTER_COORDS)
1434
+ test_shape_function_trace(test, P_1, TRI_SIDE_CENTER_COORDS)
1435
+ test_shape_function_trace(test, P_2, TRI_SIDE_CENTER_COORDS)
1436
+ test_shape_function_trace(test, P_3, TRI_SIDE_CENTER_COORDS)
1437
+ test_shape_function_gradient(test, P_1, tri_coord_sampler, tri_coord_delta_sampler)
1438
+ test_shape_function_gradient(test, P_2, tri_coord_sampler, tri_coord_delta_sampler)
1439
+ test_shape_function_gradient(test, P_3, tri_coord_sampler, tri_coord_delta_sampler)
1440
+
1441
+ P_1d = shape.TriangleNonConformingPolynomialShapeFunctions(degree=1)
1442
+ P_2d = shape.TriangleNonConformingPolynomialShapeFunctions(degree=2)
1443
+ P_3d = shape.TriangleNonConformingPolynomialShapeFunctions(degree=3)
1444
+
1445
+ test_shape_function_weight(test, P_1d, tri_coord_sampler, TRI_CENTER_COORDS)
1446
+ test_shape_function_weight(test, P_2d, tri_coord_sampler, TRI_CENTER_COORDS)
1447
+ test_shape_function_weight(test, P_3d, tri_coord_sampler, TRI_CENTER_COORDS)
1448
+ test_shape_function_gradient(test, P_1d, tri_coord_sampler, tri_coord_delta_sampler)
1449
+ test_shape_function_gradient(test, P_2d, tri_coord_sampler, tri_coord_delta_sampler)
1450
+ test_shape_function_gradient(test, P_3d, tri_coord_sampler, tri_coord_delta_sampler)
1451
+
1452
+ N1_1 = shape.TriangleNedelecFirstKindShapeFunctions(degree=1)
1453
+ test_shape_function_gradient(test, N1_1, tri_coord_sampler, tri_coord_delta_sampler, pure_curl=True)
1454
+
1455
+ RT_1 = shape.TriangleNedelecFirstKindShapeFunctions(degree=1)
1456
+ test_shape_function_gradient(test, RT_1, tri_coord_sampler, tri_coord_delta_sampler, pure_spherical=True)
1457
+
1458
+ wp.synchronize()
1459
+
1460
+
1461
+ def test_tet_shape_functions(test, device):
1462
+ TET_CENTER_COORDS = wp.constant(Coords(1 / 4.0, 1 / 4.0, 1 / 4.0))
1463
+ TET_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 1.0 / 3.0, 1.0 / 3.0))
1464
+
1465
+ @wp.func
1466
+ def tet_coord_sampler(state: wp.uint32):
1467
+ return Coords(wp.randf(state), wp.randf(state), wp.randf(state))
1468
+
1469
+ @wp.func
1470
+ def tet_coord_delta_sampler(epsilon: float, state: wp.uint32):
1471
+ param_delta = wp.normalize(wp.vec3(wp.randf(state), wp.randf(state), wp.randf(state))) * epsilon
1472
+ return param_delta, param_delta
1473
+
1474
+ P_1 = shape.TetrahedronPolynomialShapeFunctions(degree=1)
1475
+ P_2 = shape.TetrahedronPolynomialShapeFunctions(degree=2)
1476
+ P_3 = shape.TetrahedronPolynomialShapeFunctions(degree=3)
1477
+
1478
+ test_shape_function_weight(test, P_1, tet_coord_sampler, TET_CENTER_COORDS)
1479
+ test_shape_function_weight(test, P_2, tet_coord_sampler, TET_CENTER_COORDS)
1480
+ test_shape_function_weight(test, P_3, tet_coord_sampler, TET_CENTER_COORDS)
1481
+ test_shape_function_trace(test, P_1, TET_SIDE_CENTER_COORDS)
1482
+ test_shape_function_trace(test, P_2, TET_SIDE_CENTER_COORDS)
1483
+ test_shape_function_trace(test, P_3, TET_SIDE_CENTER_COORDS)
1484
+ test_shape_function_gradient(test, P_1, tet_coord_sampler, tet_coord_delta_sampler)
1485
+ test_shape_function_gradient(test, P_2, tet_coord_sampler, tet_coord_delta_sampler)
1486
+ test_shape_function_gradient(test, P_3, tet_coord_sampler, tet_coord_delta_sampler)
1487
+
1488
+ P_1d = shape.TetrahedronNonConformingPolynomialShapeFunctions(degree=1)
1489
+ P_2d = shape.TetrahedronNonConformingPolynomialShapeFunctions(degree=2)
1490
+ P_3d = shape.TetrahedronNonConformingPolynomialShapeFunctions(degree=3)
1491
+
1492
+ test_shape_function_weight(test, P_1d, tet_coord_sampler, TET_CENTER_COORDS)
1493
+ test_shape_function_weight(test, P_2d, tet_coord_sampler, TET_CENTER_COORDS)
1494
+ test_shape_function_weight(test, P_3d, tet_coord_sampler, TET_CENTER_COORDS)
1495
+ test_shape_function_gradient(test, P_1d, tet_coord_sampler, tet_coord_delta_sampler)
1496
+ test_shape_function_gradient(test, P_2d, tet_coord_sampler, tet_coord_delta_sampler)
1497
+ test_shape_function_gradient(test, P_3d, tet_coord_sampler, tet_coord_delta_sampler)
1498
+
1499
+ N1_1 = shape.TetrahedronNedelecFirstKindShapeFunctions(degree=1)
1500
+ test_shape_function_gradient(test, N1_1, tet_coord_sampler, tet_coord_delta_sampler, pure_curl=True)
1501
+
1502
+ RT_1 = shape.TetrahedronRaviartThomasShapeFunctions(degree=1)
1503
+ test_shape_function_gradient(test, RT_1, tet_coord_sampler, tet_coord_delta_sampler, pure_spherical=True)
1504
+
1505
+ wp.synchronize()
1506
+
1507
+
1508
+ def test_point_basis(test, device):
1509
+ geo = fem.Grid2D(res=wp.vec2i(2))
1510
+
1511
+ domain = fem.Cells(geo)
1512
+
1513
+ quadrature = fem.RegularQuadrature(domain, order=2, family=fem.Polynomial.GAUSS_LEGENDRE)
1514
+ point_basis = fem.PointBasisSpace(quadrature)
1515
+
1516
+ point_space = fem.make_collocated_function_space(point_basis)
1517
+ point_test = fem.make_test(point_space, domain=domain)
1518
+
1519
+ # Sample at particle positions
1520
+ ones = fem.integrate(linear_form, fields={"u": point_test}, assembly="nodal")
1521
+ test.assertAlmostEqual(np.sum(ones.numpy()), 1.0, places=5)
1522
+
1523
+ # Sampling outside of particle positions
1524
+ other_quadrature = fem.RegularQuadrature(domain, order=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1525
+ zeros = fem.integrate(linear_form, quadrature=other_quadrature, fields={"u": point_test})
1526
+
1527
+ test.assertAlmostEqual(np.sum(zeros.numpy()), 0.0, places=5)
1528
+
1529
+ # test point basis with variable points per cell
1530
+ points = wp.array([[0.25, 0.33], [0.33, 0.25], [0.8, 0.8]], dtype=wp.vec2)
1531
+ pic = fem.PicQuadrature(domain, positions=points)
1532
+
1533
+ test.assertEqual(pic.active_cell_count(), 2)
1534
+ test.assertEqual(pic.total_point_count(), 3)
1535
+ test.assertEqual(pic.max_points_per_element(), 2)
1536
+
1537
+ point_basis = fem.PointBasisSpace(pic)
1538
+ point_space = fem.make_collocated_function_space(point_basis)
1539
+ point_test = fem.make_test(point_space, domain=domain)
1540
+ test.assertEqual(point_test.space_restriction.node_count(), 3)
1541
+
1542
+ ones = fem.integrate(linear_form, fields={"u": point_test}, quadrature=pic)
1543
+ test.assertAlmostEqual(np.sum(ones.numpy()), pic.active_cell_count() / geo.cell_count(), places=5)
1544
+
1545
+ zeros = fem.integrate(linear_form, quadrature=other_quadrature, fields={"u": point_test})
1546
+ test.assertAlmostEqual(np.sum(zeros.numpy()), 0.0, places=5)
1547
+
1548
+ linear_vec = fem.make_polynomial_space(geo, dtype=wp.vec2)
1549
+ linear_test = fem.make_test(linear_vec)
1550
+ point_trial = fem.make_trial(point_space)
1551
+
1552
+ mat = fem.integrate(vector_divergence_form, fields={"u": linear_test, "q": point_trial}, quadrature=pic)
1553
+ test.assertEqual(mat.nrow, 9)
1554
+ test.assertEqual(mat.ncol, 3)
1555
+ test.assertEqual(mat.nnz_sync(), 12)
1556
+
1557
+
1558
+ @fem.integrand
1559
+ def _bicubic(s: Sample, domain: Domain):
1560
+ x = domain(s)
1561
+ return wp.pow(x[0], 3.0) * wp.pow(x[1], 3.0)
1562
+
1563
+
1564
+ @fem.integrand
1565
+ def _piecewise_constant(s: Sample):
1566
+ return float(s.element_index)
1567
+
1568
+
1569
+ def test_particle_quadratures(test, device):
1570
+ geo = fem.Grid2D(res=wp.vec2i(2))
1571
+
1572
+ domain = fem.Cells(geo)
1573
+
1574
+ # Explicit quadrature
1575
+ points, weights = domain.reference_element().prototype.instantiate_quadrature(
1576
+ order=4, family=fem.Polynomial.GAUSS_LEGENDRE
1577
+ )
1578
+ points_per_cell = len(points)
1579
+
1580
+ points = points * domain.element_count()
1581
+ weights = weights * domain.element_count()
1582
+
1583
+ points = wp.array(points, shape=(domain.element_count(), points_per_cell), dtype=Coords, device=device)
1584
+ weights = wp.array(weights, shape=(domain.element_count(), points_per_cell), dtype=float, device=device)
1585
+
1586
+ explicit_quadrature = fem.ExplicitQuadrature(domain, points, weights)
1587
+
1588
+ test.assertEqual(explicit_quadrature.max_points_per_element(), points_per_cell)
1589
+ test.assertEqual(explicit_quadrature.total_point_count(), points_per_cell * geo.cell_count())
1590
+
1591
+ # test integration accuracy
1592
+ val = fem.integrate(_bicubic, quadrature=explicit_quadrature)
1593
+ test.assertAlmostEqual(val, 1.0 / 16, places=5)
1594
+
1595
+ # test indexing validity
1596
+ arr = wp.empty(explicit_quadrature.total_point_count(), dtype=float)
1597
+ fem.interpolate(_piecewise_constant, dest=arr, quadrature=explicit_quadrature)
1598
+ assert_np_equal(arr.numpy(), np.arange(geo.cell_count()).repeat(points_per_cell))
1599
+
1600
+ # PIC quadrature
1601
+ element_indices = wp.array([3, 3, 2], dtype=int, device=device)
1602
+ element_coords = wp.array(
1603
+ [
1604
+ [0.25, 0.5, 0.0],
1605
+ [0.5, 0.25, 0.0],
1606
+ [0.5, 0.5, 0.0],
1607
+ ],
1608
+ dtype=Coords,
1609
+ device=device,
1610
+ )
1611
+
1612
+ pic_quadrature = fem.PicQuadrature(domain, positions=(element_indices, element_coords))
1613
+
1614
+ test.assertEqual(pic_quadrature.max_points_per_element(), 2)
1615
+ test.assertEqual(pic_quadrature.total_point_count(), 3)
1616
+ test.assertEqual(pic_quadrature.active_cell_count(), 2)
1617
+
1618
+ # Test integration accuracy
1619
+ val = fem.integrate(_piecewise_constant, quadrature=pic_quadrature)
1620
+ test.assertAlmostEqual(val, 1.25, places=5)
1621
+
1622
+ # Test differentiability of PicQuadrature w.r.t positions and measures
1623
+ points = wp.array([[0.25, 0.33], [0.33, 0.25], [0.8, 0.8]], dtype=wp.vec2, device=device, requires_grad=True)
1624
+ measures = wp.ones(3, dtype=float, device=device, requires_grad=True)
1625
+
1626
+ tape = wp.Tape()
1627
+ with tape:
1628
+ pic = fem.PicQuadrature(domain, positions=points, measures=measures, requires_grad=True)
1629
+
1630
+ pic.arg_value(device).particle_coords.grad.fill_(1.0)
1631
+ pic.arg_value(device).particle_fraction.grad.fill_(1.0)
1632
+ tape.backward()
1633
+
1634
+ assert_np_equal(points.grad.numpy(), np.full((3, 2), 2.0)) # == 1.0 / cell_size
1635
+ assert_np_equal(measures.grad.numpy(), np.full(3, 4.0)) # == 1.0 / cell_area
1636
+
1637
+
1638
+ @fem.integrand(kernel_options={"enable_backward": False})
1639
+ def _value_at_node(domain: fem.Domain, s: fem.Sample, f: fem.Field, values: wp.array(dtype=float)):
1640
+ # lookup at node is ambiguous, check that partition_lookup retains sample on current partition
1641
+ s_partition = fem.partition_lookup(domain, domain(s))
1642
+ wp.expect_eq(s.element_index, s_partition.element_index)
1643
+ wp.expect_neq(fem.operator.element_partition_index(domain, s.element_index), fem.NULL_ELEMENT_INDEX)
1644
+
1645
+ node_index = fem.operator.node_partition_index(f, s.qp_index)
1646
+ return values[node_index]
1647
+
1648
+
1649
+ @fem.integrand(kernel_options={"enable_backward": False})
1650
+ def _test_node_index(s: fem.Sample, u: fem.Field):
1651
+ wp.expect_eq(fem.node_index(u, s), s.qp_index)
1652
+ return 0.0
1653
+
1654
+
1655
+ def test_nodal_quadrature(test, device):
1656
+ geo = fem.Grid2D(res=wp.vec2i(2))
1657
+
1658
+ domain = fem.Cells(geo)
1659
+
1660
+ space = fem.make_polynomial_space(geo, degree=2, discontinuous=True, family=fem.Polynomial.GAUSS_LEGENDRE)
1661
+ nodal_quadrature = fem.NodalQuadrature(domain, space)
1662
+
1663
+ test.assertEqual(nodal_quadrature.max_points_per_element(), 9)
1664
+ test.assertEqual(nodal_quadrature.total_point_count(), 9 * geo.cell_count())
1665
+
1666
+ val = fem.integrate(_bicubic, quadrature=nodal_quadrature)
1667
+ test.assertAlmostEqual(val, 1.0 / 16, places=5)
1668
+
1669
+ # test accessing data associated to a given node
1670
+
1671
+ piecewise_constant_space = fem.make_polynomial_space(geo, degree=1)
1672
+ geo_partition = fem.LinearGeometryPartition(geo, 2, 4)
1673
+ assert geo_partition.cell_count() == 1
1674
+
1675
+ space_partition = fem.make_space_partition(piecewise_constant_space, geo_partition, with_halo=False)
1676
+
1677
+ field = fem.make_discrete_field(piecewise_constant_space, space_partition=space_partition)
1678
+
1679
+ partition_domain = fem.Cells(geo_partition)
1680
+ partition_nodal_quadrature = fem.NodalQuadrature(partition_domain, piecewise_constant_space)
1681
+
1682
+ partition_node_values = wp.full(value=5.0, shape=space_partition.node_count(), dtype=float)
1683
+ val = fem.integrate(
1684
+ _value_at_node,
1685
+ quadrature=partition_nodal_quadrature,
1686
+ fields={"f": field},
1687
+ values={"values": partition_node_values},
1688
+ )
1689
+ test.assertAlmostEqual(val, 5.0 / geo.cell_count(), places=5)
1690
+
1691
+ u_test = fem.make_test(space)
1692
+ fem.integrate(_test_node_index, assembly="nodal", fields={"u": u_test})
1693
+
1694
+
1695
+ @wp.func
1696
+ def aniso_bicubic_fn(x: wp.vec2, scale: wp.vec2):
1697
+ return wp.pow(x[0] * scale[0], 3.0) * wp.pow(x[1] * scale[1], 3.0)
1698
+
1699
+
1700
+ @wp.func
1701
+ def aniso_bicubic_grad(x: wp.vec2, scale: wp.vec2):
1702
+ return wp.vec2(
1703
+ 3.0 * scale[0] * wp.pow(x[0] * scale[0], 2.0) * wp.pow(x[1] * scale[1], 3.0),
1704
+ 3.0 * scale[1] * wp.pow(x[0] * scale[0], 3.0) * wp.pow(x[1] * scale[1], 2.0),
1705
+ )
1706
+
1707
+
1708
+ def test_implicit_fields(test, device):
1709
+ geo = fem.Grid2D(res=wp.vec2i(2))
1710
+ domain = fem.Cells(geo)
1711
+ boundary = fem.BoundarySides(geo)
1712
+
1713
+ space = fem.make_polynomial_space(geo)
1714
+ vec_space = fem.make_polynomial_space(geo, dtype=wp.vec2)
1715
+ discrete_field = fem.make_discrete_field(space)
1716
+ discrete_vec_field = fem.make_discrete_field(vec_space)
1717
+
1718
+ # Uniform
1719
+
1720
+ uniform = fem.UniformField(domain, 5.0)
1721
+ fem.interpolate(uniform, dest=discrete_field)
1722
+ assert_np_equal(discrete_field.dof_values.numpy(), np.full(9, 5.0))
1723
+
1724
+ fem.interpolate(grad_field, fields={"p": uniform}, dest=discrete_vec_field)
1725
+ assert_np_equal(discrete_vec_field.dof_values.numpy(), np.zeros((9, 2)))
1726
+
1727
+ uniform.value = 2.0
1728
+ fem.interpolate(uniform.trace(), dest=fem.make_restriction(discrete_field, domain=boundary))
1729
+ assert_np_equal(discrete_field.dof_values.numpy(), np.array([2.0] * 4 + [5.0] + [2.0] * 4))
1730
+
1731
+ # Implicit
1732
+
1733
+ implicit = fem.ImplicitField(
1734
+ domain, func=aniso_bicubic_fn, values={"scale": wp.vec2(2.0, 4.0)}, grad_func=aniso_bicubic_grad
1735
+ )
1736
+ fem.interpolate(implicit, dest=discrete_field)
1737
+ assert_np_equal(
1738
+ discrete_field.dof_values.numpy(),
1739
+ np.array([0.0, 0.0, 0.0, 0.0, 2.0**3, 4.0**3, 0.0, 2.0**3 * 2.0**3, 4.0**3 * 2.0**3]),
1740
+ )
1741
+
1742
+ fem.interpolate(grad_field, fields={"p": implicit}, dest=discrete_vec_field)
1743
+ assert_np_equal(discrete_vec_field.dof_values.numpy()[0], np.zeros(2))
1744
+ assert_np_equal(discrete_vec_field.dof_values.numpy()[-1], np.full(2, (2.0**9.0 * 3.0)))
1745
+
1746
+ implicit.values.scale = wp.vec2(-2.0, -2.0)
1747
+ fem.interpolate(implicit.trace(), dest=fem.make_restriction(discrete_field, domain=boundary))
1748
+ assert_np_equal(
1749
+ discrete_field.dof_values.numpy(),
1750
+ np.array([0.0, 0.0, 0.0, 0.0, 2.0**3, 2.0**3, 0.0, 2.0**3, 4.0**3]),
1751
+ )
1752
+
1753
+ # Nonconforming
1754
+
1755
+ geo2 = fem.Grid2D(res=wp.vec2i(1), bounds_lo=wp.vec2(0.25, 0.25), bounds_hi=wp.vec2(2.0, 2.0))
1756
+ domain2 = fem.Cells(geo2)
1757
+ boundary2 = fem.BoundarySides(geo2)
1758
+ space2 = fem.make_polynomial_space(geo2)
1759
+ vec_space2 = fem.make_polynomial_space(geo2, dtype=wp.vec2)
1760
+ discrete_field2 = fem.make_discrete_field(space2)
1761
+ discrete_vec_field2 = fem.make_discrete_field(vec_space2)
1762
+
1763
+ nonconforming = fem.NonconformingField(domain2, discrete_field, background=5.0)
1764
+ fem.interpolate(
1765
+ nonconforming,
1766
+ dest=discrete_field2,
1767
+ )
1768
+ assert_np_equal(discrete_field2.dof_values.numpy(), np.array([2.0] + [5.0] * 3))
1769
+
1770
+ fem.interpolate(grad_field, fields={"p": nonconforming}, dest=discrete_vec_field2)
1771
+ assert_np_equal(discrete_vec_field2.dof_values.numpy()[0], np.full(2, 8.0))
1772
+ assert_np_equal(discrete_vec_field2.dof_values.numpy()[-1], np.zeros(2))
1773
+
1774
+ discrete_field2.dof_values.zero_()
1775
+ fem.interpolate(
1776
+ nonconforming.trace(),
1777
+ dest=fem.make_restriction(discrete_field2, domain=boundary2),
1778
+ )
1779
+ assert_np_equal(discrete_field2.dof_values.numpy(), np.array([2.0] + [5.0] * 3))
1780
+
1781
+
1782
+ @fem.integrand
1783
+ def _expect_pure_curl(s: fem.Sample, field: fem.Field):
1784
+ sym_grad = fem.D(field, s)
1785
+ wp.expect_near(wp.ddot(sym_grad, sym_grad), 0.0)
1786
+ return 0.0
1787
+
1788
+
1789
+ @fem.integrand
1790
+ def _expect_pure_spherical(s: fem.Sample, field: fem.Field):
1791
+ grad = fem.grad(field, s)
1792
+ deviatoric_part = grad - spherical_part(grad)
1793
+ wp.expect_near(wp.ddot(deviatoric_part, deviatoric_part), 0.0)
1794
+ return 0.0
1795
+
1796
+
1797
+ @fem.integrand
1798
+ def _expect_normal_continuity(s: fem.Sample, domain: fem.Domain, field: fem.Field):
1799
+ nor = fem.normal(domain, s)
1800
+ wp.expect_near(wp.dot(fem.inner(field, s), nor), wp.dot(fem.outer(field, s), nor), 0.0001)
1801
+ return 0.0
1802
+
1803
+
1804
+ @fem.integrand
1805
+ def _expect_tangential_continuity(s: fem.Sample, domain: fem.Domain, field: fem.Field):
1806
+ nor = fem.normal(domain, s)
1807
+ in_s = fem.inner(field, s)
1808
+ out_s = fem.outer(field, s)
1809
+ in_t = in_s - wp.dot(in_s, nor) * nor
1810
+ out_t = out_s - wp.dot(out_s, nor) * nor
1811
+
1812
+ _expect_near(in_t, out_t, 0.0001)
1813
+ return 0.0
1814
+
1815
+
1816
+ def test_vector_spaces(test, device):
1817
+ # Test covariant / contravariant mappings
1818
+
1819
+ with wp.ScopedDevice(device):
1820
+ positions, hex_vidx = _gen_quadmesh(3)
1821
+
1822
+ geo = fem.Quadmesh2D(quad_vertex_indices=hex_vidx, positions=positions)
1823
+
1824
+ curl_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.NEDELEC_FIRST_KIND)
1825
+ curl_test = fem.make_test(curl_space)
1826
+
1827
+ curl_field = curl_space.make_field()
1828
+ curl_field.dof_values = wp.array(np.linspace(0.0, 1.0, curl_space.node_count()), dtype=float)
1829
+
1830
+ fem.interpolate(
1831
+ _expect_tangential_continuity,
1832
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=2),
1833
+ fields={"field": curl_field.trace()},
1834
+ )
1835
+
1836
+ div_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.RAVIART_THOMAS)
1837
+ div_test = fem.make_test(div_space)
1838
+
1839
+ div_field = div_space.make_field()
1840
+ div_field.dof_values = wp.array(np.linspace(0.0, 1.0, div_space.node_count()), dtype=float)
1841
+
1842
+ fem.interpolate(
1843
+ _expect_normal_continuity,
1844
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=2),
1845
+ fields={"field": div_field.trace()},
1846
+ )
1847
+
1848
+ with wp.ScopedDevice(device):
1849
+ positions, hex_vidx = _gen_hexmesh(3)
1850
+
1851
+ geo = fem.Hexmesh(hex_vertex_indices=hex_vidx, positions=positions)
1852
+
1853
+ curl_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.NEDELEC_FIRST_KIND)
1854
+ curl_test = fem.make_test(curl_space)
1855
+
1856
+ curl_field = curl_space.make_field()
1857
+ curl_field.dof_values = wp.array(np.linspace(0.0, 1.0, curl_space.node_count()), dtype=float)
1858
+
1859
+ fem.interpolate(
1860
+ _expect_tangential_continuity,
1861
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=2),
1862
+ fields={"field": curl_field.trace()},
1863
+ )
1864
+
1865
+ div_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.RAVIART_THOMAS)
1866
+ div_test = fem.make_test(div_space)
1867
+
1868
+ div_field = div_space.make_field()
1869
+ div_field.dof_values = wp.array(np.linspace(0.0, 1.0, div_space.node_count()), dtype=float)
1870
+
1871
+ fem.interpolate(
1872
+ _expect_normal_continuity,
1873
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=2),
1874
+ fields={"field": div_field.trace()},
1875
+ )
1876
+
1877
+ with wp.ScopedDevice(device):
1878
+ positions, tri_vidx = _gen_trimesh(3, 5)
1879
+
1880
+ geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=positions)
1881
+
1882
+ curl_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.NEDELEC_FIRST_KIND)
1883
+ curl_test = fem.make_test(curl_space)
1884
+
1885
+ fem.integrate(_expect_pure_curl, fields={"field": curl_test}, assembly="generic")
1886
+
1887
+ curl_field = curl_space.make_field()
1888
+ curl_field.dof_values.fill_(1.0)
1889
+ fem.interpolate(
1890
+ _expect_pure_curl, quadrature=fem.RegularQuadrature(fem.Cells(geo), order=2), fields={"field": curl_field}
1891
+ )
1892
+
1893
+ fem.interpolate(
1894
+ _expect_tangential_continuity,
1895
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=2),
1896
+ fields={"field": curl_field.trace()},
1897
+ )
1898
+
1899
+ div_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.RAVIART_THOMAS)
1900
+ div_test = fem.make_test(div_space)
1901
+
1902
+ fem.integrate(_expect_pure_spherical, fields={"field": div_test}, assembly="generic")
1903
+
1904
+ div_field = div_space.make_field()
1905
+ div_field.dof_values.fill_(1.0)
1906
+ fem.interpolate(
1907
+ _expect_pure_spherical,
1908
+ quadrature=fem.RegularQuadrature(fem.Cells(geo), order=2),
1909
+ fields={"field": div_field},
1910
+ )
1911
+
1912
+ fem.interpolate(
1913
+ _expect_normal_continuity,
1914
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=2),
1915
+ fields={"field": div_field.trace()},
1916
+ )
1917
+
1918
+ with wp.ScopedDevice(device):
1919
+ positions, tet_vidx = _gen_tetmesh(3, 5, 7)
1920
+
1921
+ geo = fem.Tetmesh(tet_vertex_indices=tet_vidx, positions=positions)
1922
+
1923
+ curl_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.NEDELEC_FIRST_KIND)
1924
+ curl_test = fem.make_test(curl_space)
1925
+
1926
+ fem.integrate(_expect_pure_curl, fields={"field": curl_test}, assembly="generic")
1927
+
1928
+ curl_field = curl_space.make_field()
1929
+ curl_field.dof_values.fill_(1.0)
1930
+ fem.interpolate(
1931
+ _expect_pure_curl, quadrature=fem.RegularQuadrature(fem.Cells(geo), order=2), fields={"field": curl_field}
1932
+ )
1933
+
1934
+ fem.interpolate(
1935
+ _expect_tangential_continuity,
1936
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=1),
1937
+ fields={"field": curl_field.trace()},
1938
+ )
1939
+
1940
+ div_space = fem.make_polynomial_space(geo, element_basis=fem.ElementBasis.RAVIART_THOMAS)
1941
+ div_test = fem.make_test(div_space)
1942
+
1943
+ fem.integrate(_expect_pure_spherical, fields={"field": div_test}, assembly="generic")
1944
+
1945
+ div_field = div_space.make_field()
1946
+ div_field.dof_values.fill_(1.0)
1947
+ fem.interpolate(
1948
+ _expect_pure_spherical,
1949
+ quadrature=fem.RegularQuadrature(fem.Cells(geo), order=2),
1950
+ fields={"field": div_field},
1951
+ )
1952
+
1953
+ fem.interpolate(
1954
+ _expect_normal_continuity,
1955
+ quadrature=fem.RegularQuadrature(fem.Sides(geo), order=0),
1956
+ fields={"field": div_field.trace()},
1957
+ )
1958
+
1959
+
1960
+ @wp.kernel(enable_backward=False)
1961
+ def test_qr_eigenvalues():
1962
+ tol = 5.0e-7
1963
+
1964
+ # zero
1965
+ Zero = wp.mat33(0.0)
1966
+ Id = wp.identity(n=3, dtype=float)
1967
+ D3, P3 = symmetric_eigenvalues_qr(Zero, tol * tol)
1968
+ wp.expect_eq(D3, wp.vec3(0.0))
1969
+ wp.expect_eq(P3, Id)
1970
+
1971
+ # Identity
1972
+ D3, P3 = symmetric_eigenvalues_qr(Id, tol * tol)
1973
+ wp.expect_eq(D3, wp.vec3(1.0))
1974
+ wp.expect_eq(wp.transpose(P3) * P3, Id)
1975
+
1976
+ # rank 1
1977
+ v = wp.vec4(0.0, 1.0, 1.0, 0.0)
1978
+ Rank1 = wp.outer(v, v)
1979
+ D4, P4 = symmetric_eigenvalues_qr(Rank1, tol * tol)
1980
+ wp.expect_near(wp.max(D4), wp.length_sq(v), tol)
1981
+ Err4 = wp.transpose(P4) * wp.diag(D4) * P4 - Rank1
1982
+ wp.expect_near(wp.ddot(Err4, Err4), 0.0, tol)
1983
+
1984
+ # rank 2
1985
+ v2 = wp.vec4(0.0, 0.5, -0.5, 0.0)
1986
+ Rank2 = Rank1 + wp.outer(v2, v2)
1987
+ D4, P4 = symmetric_eigenvalues_qr(Rank2, tol * tol)
1988
+ wp.expect_near(wp.max(D4), wp.length_sq(v), tol)
1989
+ wp.expect_near(D4[0] + D4[1] + D4[2] + D4[3], wp.length_sq(v) + wp.length_sq(v2), tol)
1990
+ Err4 = wp.transpose(P4) * wp.diag(D4) * P4 - Rank2
1991
+ wp.expect_near(wp.ddot(Err4, Err4), 0.0, tol)
1992
+
1993
+ # rank 4
1994
+ v3 = wp.vec4(1.0, 2.0, 3.0, 4.0)
1995
+ v4 = wp.vec4(2.0, 1.0, 0.0, -1.0)
1996
+ Rank4 = Rank2 + wp.outer(v3, v3) + wp.outer(v4, v4)
1997
+ D4, P4 = symmetric_eigenvalues_qr(Rank4, tol * tol)
1998
+ Err4 = wp.transpose(P4) * wp.diag(D4) * P4 - Rank4
1999
+ wp.expect_near(wp.ddot(Err4, Err4), 0.0, tol)
2000
+
2001
+ # test robustness to low requested tolerance
2002
+ Rank6 = wp.matrix_from_cols(
2003
+ vec6f(0.00171076, 0.0, 0.0, 0.0, 0.0, 0.0),
2004
+ vec6f(0.0, 0.00169935, 6.14367e-06, -3.52589e-05, 3.02397e-05, -1.53458e-11),
2005
+ vec6f(0.0, 6.14368e-06, 0.00172217, 2.03568e-05, 1.74589e-05, -2.92627e-05),
2006
+ vec6f(0.0, -3.52589e-05, 2.03568e-05, 0.00172178, 2.53422e-05, 3.02397e-05),
2007
+ vec6f(0.0, 3.02397e-05, 1.74589e-05, 2.53422e-05, 0.00171114, 3.52589e-05),
2008
+ vec6f(0.0, 6.42993e-12, -2.92627e-05, 3.02397e-05, 3.52589e-05, 0.00169935),
2009
+ )
2010
+ D6, P6 = symmetric_eigenvalues_qr(Rank6, 0.0)
2011
+ Err6 = wp.transpose(P6) * wp.diag(D6) * P6 - Rank6
2012
+ wp.expect_near(wp.ddot(Err6, Err6), 0.0, 1.0e-13)
2013
+
2014
+
2015
+ @wp.kernel(enable_backward=False)
2016
+ def test_qr_inverse():
2017
+ rng = wp.rand_init(4356, wp.tid())
2018
+ M = wp.mat33(
2019
+ wp.randf(rng, 0.0, 10.0),
2020
+ wp.randf(rng, 0.0, 10.0),
2021
+ wp.randf(rng, 0.0, 10.0),
2022
+ wp.randf(rng, 0.0, 10.0),
2023
+ wp.randf(rng, 0.0, 10.0),
2024
+ wp.randf(rng, 0.0, 10.0),
2025
+ wp.randf(rng, 0.0, 10.0),
2026
+ wp.randf(rng, 0.0, 10.0),
2027
+ wp.randf(rng, 0.0, 10.0),
2028
+ )
2029
+
2030
+ if wp.determinant(M) != 0.0:
2031
+ tol = 1.0e-8
2032
+ Mi = inverse_qr(M)
2033
+ Id = wp.identity(n=3, dtype=float)
2034
+ Err = M * Mi - Id
2035
+ wp.expect_near(wp.ddot(Err, Err), 0.0, tol)
2036
+ Err = Mi * M - Id
2037
+ wp.expect_near(wp.ddot(Err, Err), 0.0, tol)
2038
+
2039
+
2040
+ def test_array_axpy(test, device):
2041
+ N = 10
2042
+ alpha = 0.5
2043
+ beta = 4.0
2044
+
2045
+ x = wp.full(N, 2.0, device=device, dtype=float, requires_grad=True)
2046
+ y = wp.array(np.arange(N), device=device, dtype=wp.float64, requires_grad=True)
2047
+
2048
+ tape = wp.Tape()
2049
+ with tape:
2050
+ fem.linalg.array_axpy(x=x, y=y, alpha=alpha, beta=beta)
2051
+
2052
+ assert_np_equal(x.numpy(), np.full(N, 2.0))
2053
+ assert_np_equal(y.numpy(), alpha * x.numpy() + beta * np.arange(N))
2054
+
2055
+ y.grad.fill_(1.0)
2056
+ tape.backward()
2057
+
2058
+ assert_np_equal(x.grad.numpy(), alpha * np.ones(N))
2059
+ assert_np_equal(y.grad.numpy(), beta * np.ones(N))
2060
+
2061
+
2062
+ def test_integrate_high_order(test_field, device):
2063
+ with wp.ScopedDevice(device):
2064
+ geo = fem.Grid3D(res=(1, 1, 1))
2065
+ space = fem.make_polynomial_space(geo, degree=4)
2066
+ test_field = fem.make_test(space)
2067
+ trial_field = fem.make_trial(space)
2068
+
2069
+ # compare consistency of tile-based "dispatch" assembly and generic
2070
+ v0 = fem.integrate(
2071
+ linear_form, fields={"u": test_field}, assembly="dispatch", kernel_options={"enable_backward": False}
2072
+ )
2073
+ v1 = fem.integrate(
2074
+ linear_form, fields={"u": test_field}, assembly="generic", kernel_options={"enable_backward": False}
2075
+ )
2076
+
2077
+ assert_np_equal(v0.numpy(), v1.numpy(), tol=1.0e-6)
2078
+
2079
+ h0 = fem.integrate(
2080
+ bilinear_form,
2081
+ fields={"v": test_field, "u": trial_field},
2082
+ assembly="dispatch",
2083
+ kernel_options={"enable_backward": False},
2084
+ )
2085
+ h1 = fem.integrate(
2086
+ bilinear_form,
2087
+ fields={"v": test_field, "u": trial_field},
2088
+ assembly="generic",
2089
+ kernel_options={"enable_backward": False},
2090
+ )
2091
+
2092
+ h0_nnz = h0.nnz_sync()
2093
+ h1_nnz = h1.nnz_sync()
2094
+ assert h0.shape == h1.shape
2095
+ assert h0_nnz == h1_nnz
2096
+ assert_array_equal(h0.offsets[: h0.nrow + 1], h1.offsets[: h1.nrow + 1])
2097
+ assert_array_equal(h0.columns[:h0_nnz], h1.columns[:h1_nnz])
2098
+ assert_np_equal(h0.values[:h0_nnz].numpy(), h1.values[:h1_nnz].numpy(), tol=1.0e-6)
2099
+
2100
+
2101
+ def test_capturability(test, device):
2102
+ A = bsr_zeros(0, 0, block_type=wp.float32, device=device)
2103
+
2104
+ def test_body():
2105
+ geo = fem.Grid3D(res=(4, 4, 4))
2106
+ space = fem.make_polynomial_space(geo, degree=1)
2107
+
2108
+ cell_mask = wp.zeros(geo.cell_count(), dtype=int, device=device)
2109
+ cell_mask[16:32].fill_(1)
2110
+
2111
+ geo_partition = fem.ExplicitGeometryPartition(geo, cell_mask, max_cell_count=32, max_side_count=0)
2112
+ space_partition = fem.make_space_partition(space, geo_partition, with_halo=False, max_node_count=64)
2113
+
2114
+ test_field = fem.make_test(space, space_partition=space_partition)
2115
+ trial_field = fem.make_trial(space, space_partition=space_partition)
2116
+ bsr_set_zero(
2117
+ A,
2118
+ rows_of_blocks=test_field.space_partition.node_count(),
2119
+ cols_of_blocks=trial_field.space_partition.node_count(),
2120
+ )
2121
+ fem.integrate(
2122
+ bilinear_form,
2123
+ fields={"v": test_field, "u": trial_field},
2124
+ kernel_options={"enable_backward": False},
2125
+ output=A,
2126
+ )
2127
+
2128
+ with wp.ScopedDevice(device):
2129
+ test_body()
2130
+ assert A.shape == (64, 64)
2131
+ nnz_ref = A.nnz_sync()
2132
+ values_ref = A.values.numpy()[:nnz_ref]
2133
+ columns_ref = A.columns.numpy()[:nnz_ref]
2134
+ bsr_set_zero(A)
2135
+ assert A.nnz_sync() == 0
2136
+
2137
+ with wp.ScopedCapture() as capture:
2138
+ test_body()
2139
+ wp.capture_launch(capture.graph)
2140
+ assert A.nnz_sync() == nnz_ref
2141
+ assert_np_equal(A.values.numpy()[:nnz_ref], values_ref)
2142
+ assert_np_equal(A.columns.numpy()[:nnz_ref], columns_ref)
2143
+
2144
+
2145
+ devices = get_test_devices()
2146
+ cuda_devices = get_selected_cuda_test_devices()
2147
+
2148
+
2149
+ class TestFem(unittest.TestCase):
2150
+ pass
2151
+
2152
+
2153
+ add_function_test(TestFem, "test_regular_quadrature", test_regular_quadrature)
2154
+ add_function_test(TestFem, "test_closest_point_queries", test_closest_point_queries)
2155
+ add_function_test(TestFem, "test_grad_decomposition", test_grad_decomposition, devices=devices)
2156
+ add_function_test(TestFem, "test_integrate_gradient", test_integrate_gradient, devices=devices)
2157
+ add_function_test(TestFem, "test_interpolate_gradient", test_interpolate_gradient, devices=devices)
2158
+ add_function_test(TestFem, "test_vector_divergence_theorem", test_vector_divergence_theorem, devices=devices)
2159
+ add_function_test(TestFem, "test_tensor_divergence_theorem", test_tensor_divergence_theorem, devices=devices)
2160
+ add_function_test(TestFem, "test_grid_2d", test_grid_2d, devices=devices)
2161
+ add_function_test(TestFem, "test_triangle_mesh", test_triangle_mesh, devices=devices)
2162
+ add_function_test(TestFem, "test_quad_mesh", test_quad_mesh, devices=devices)
2163
+ add_function_test(TestFem, "test_grid_3d", test_grid_3d, devices=devices)
2164
+ add_function_test(TestFem, "test_tet_mesh", test_tet_mesh, devices=devices)
2165
+ add_function_test(TestFem, "test_hex_mesh", test_hex_mesh, devices=devices)
2166
+ add_function_test(TestFem, "test_nanogrid", test_nanogrid, devices=cuda_devices)
2167
+ add_function_test(TestFem, "test_adaptive_nanogrid", test_adaptive_nanogrid, devices=cuda_devices)
2168
+ add_function_test(TestFem, "test_deformed_geometry", test_deformed_geometry, devices=devices)
2169
+ add_function_test(
2170
+ TestFem, "test_deformed_geometry_codimensional", test_deformed_geometry_codimensional, devices=devices
2171
+ )
2172
+ add_function_test(TestFem, "test_vector_spaces", test_vector_spaces, devices=devices)
2173
+ add_function_test(TestFem, "test_dof_mapper", test_dof_mapper)
2174
+ add_function_test(TestFem, "test_point_basis", test_point_basis)
2175
+ add_function_test(TestFem, "test_particle_quadratures", test_particle_quadratures)
2176
+ add_function_test(TestFem, "test_nodal_quadrature", test_nodal_quadrature)
2177
+ add_function_test(TestFem, "test_implicit_fields", test_implicit_fields)
2178
+ add_function_test(TestFem, "test_integrate_high_order", test_integrate_high_order, devices=cuda_devices)
2179
+
2180
+
2181
+ class TestFemUtilities(unittest.TestCase):
2182
+ pass
2183
+
2184
+
2185
+ add_kernel_test(TestFemUtilities, test_qr_eigenvalues, dim=1, devices=devices)
2186
+
2187
+ add_kernel_test(TestFemUtilities, test_qr_inverse, dim=100, devices=devices)
2188
+ add_function_test(TestFemUtilities, "test_array_axpy", test_array_axpy)
2189
+
2190
+
2191
+ class TestFemShapeFunctions(unittest.TestCase):
2192
+ pass
2193
+
2194
+
2195
+ add_function_test(TestFemShapeFunctions, "test_square_shape_functions", test_square_shape_functions)
2196
+ add_function_test(TestFemShapeFunctions, "test_cube_shape_functions", test_cube_shape_functions)
2197
+ add_function_test(TestFemShapeFunctions, "test_tri_shape_functions", test_tri_shape_functions)
2198
+ add_function_test(TestFemShapeFunctions, "test_tet_shape_functions", test_tet_shape_functions)
2199
+
2200
+ add_function_test(TestFemShapeFunctions, "test_capturability", test_capturability, devices=cuda_devices)
2201
+
2202
+ if __name__ == "__main__":
2203
+ wp.clear_kernel_cache()
2204
+ unittest.main(verbosity=2, failfast=True)