warp-lang 0.9.0__py3-none-win_amd64.whl → 0.11.0__py3-none-win_amd64.whl

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

Potentially problematic release.


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

Files changed (315) hide show
  1. warp/__init__.py +15 -7
  2. warp/__init__.pyi +1 -0
  3. warp/bin/warp-clang.dll +0 -0
  4. warp/bin/warp.dll +0 -0
  5. warp/build.py +22 -443
  6. warp/build_dll.py +384 -0
  7. warp/builtins.py +998 -488
  8. warp/codegen.py +1307 -739
  9. warp/config.py +5 -3
  10. warp/constants.py +6 -0
  11. warp/context.py +1291 -548
  12. warp/dlpack.py +31 -31
  13. warp/fabric.py +326 -0
  14. warp/fem/__init__.py +27 -0
  15. warp/fem/cache.py +389 -0
  16. warp/fem/dirichlet.py +181 -0
  17. warp/fem/domain.py +263 -0
  18. warp/fem/field/__init__.py +101 -0
  19. warp/fem/field/field.py +149 -0
  20. warp/fem/field/nodal_field.py +299 -0
  21. warp/fem/field/restriction.py +21 -0
  22. warp/fem/field/test.py +181 -0
  23. warp/fem/field/trial.py +183 -0
  24. warp/fem/geometry/__init__.py +19 -0
  25. warp/fem/geometry/closest_point.py +70 -0
  26. warp/fem/geometry/deformed_geometry.py +271 -0
  27. warp/fem/geometry/element.py +744 -0
  28. warp/fem/geometry/geometry.py +186 -0
  29. warp/fem/geometry/grid_2d.py +373 -0
  30. warp/fem/geometry/grid_3d.py +435 -0
  31. warp/fem/geometry/hexmesh.py +953 -0
  32. warp/fem/geometry/partition.py +376 -0
  33. warp/fem/geometry/quadmesh_2d.py +532 -0
  34. warp/fem/geometry/tetmesh.py +840 -0
  35. warp/fem/geometry/trimesh_2d.py +577 -0
  36. warp/fem/integrate.py +1616 -0
  37. warp/fem/operator.py +191 -0
  38. warp/fem/polynomial.py +213 -0
  39. warp/fem/quadrature/__init__.py +2 -0
  40. warp/fem/quadrature/pic_quadrature.py +245 -0
  41. warp/fem/quadrature/quadrature.py +294 -0
  42. warp/fem/space/__init__.py +292 -0
  43. warp/fem/space/basis_space.py +489 -0
  44. warp/fem/space/collocated_function_space.py +105 -0
  45. warp/fem/space/dof_mapper.py +236 -0
  46. warp/fem/space/function_space.py +145 -0
  47. warp/fem/space/grid_2d_function_space.py +267 -0
  48. warp/fem/space/grid_3d_function_space.py +306 -0
  49. warp/fem/space/hexmesh_function_space.py +352 -0
  50. warp/fem/space/partition.py +350 -0
  51. warp/fem/space/quadmesh_2d_function_space.py +369 -0
  52. warp/fem/space/restriction.py +160 -0
  53. warp/fem/space/shape/__init__.py +15 -0
  54. warp/fem/space/shape/cube_shape_function.py +738 -0
  55. warp/fem/space/shape/shape_function.py +103 -0
  56. warp/fem/space/shape/square_shape_function.py +611 -0
  57. warp/fem/space/shape/tet_shape_function.py +567 -0
  58. warp/fem/space/shape/triangle_shape_function.py +429 -0
  59. warp/fem/space/tetmesh_function_space.py +292 -0
  60. warp/fem/space/topology.py +295 -0
  61. warp/fem/space/trimesh_2d_function_space.py +221 -0
  62. warp/fem/types.py +77 -0
  63. warp/fem/utils.py +495 -0
  64. warp/native/array.h +164 -55
  65. warp/native/builtin.h +150 -174
  66. warp/native/bvh.cpp +75 -328
  67. warp/native/bvh.cu +406 -23
  68. warp/native/bvh.h +37 -45
  69. warp/native/clang/clang.cpp +136 -24
  70. warp/native/crt.cpp +1 -76
  71. warp/native/crt.h +111 -104
  72. warp/native/cuda_crt.h +1049 -0
  73. warp/native/cuda_util.cpp +15 -3
  74. warp/native/cuda_util.h +3 -1
  75. warp/native/cutlass/tools/library/scripts/conv2d_operation.py +463 -0
  76. warp/native/cutlass/tools/library/scripts/conv3d_operation.py +321 -0
  77. warp/native/cutlass/tools/library/scripts/gemm_operation.py +988 -0
  78. warp/native/cutlass/tools/library/scripts/generator.py +4625 -0
  79. warp/native/cutlass/tools/library/scripts/library.py +799 -0
  80. warp/native/cutlass/tools/library/scripts/manifest.py +402 -0
  81. warp/native/cutlass/tools/library/scripts/pycutlass/docs/source/conf.py +96 -0
  82. warp/native/cutlass/tools/library/scripts/pycutlass/profile/conv/conv2d_f16_sm80.py +106 -0
  83. warp/native/cutlass/tools/library/scripts/pycutlass/profile/gemm/gemm_f32_sm80.py +91 -0
  84. warp/native/cutlass/tools/library/scripts/pycutlass/setup.py +80 -0
  85. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/__init__.py +48 -0
  86. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/arguments.py +118 -0
  87. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/c_types.py +241 -0
  88. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/compiler.py +432 -0
  89. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/conv2d_operation.py +631 -0
  90. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/epilogue.py +1026 -0
  91. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/frontend.py +104 -0
  92. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/gemm_operation.py +1276 -0
  93. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/library.py +744 -0
  94. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/memory_manager.py +74 -0
  95. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/operation.py +110 -0
  96. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/parser.py +619 -0
  97. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/reduction_operation.py +398 -0
  98. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/tensor_ref.py +70 -0
  99. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/test/__init__.py +4 -0
  100. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/test/conv2d_testbed.py +646 -0
  101. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/test/gemm_grouped_testbed.py +235 -0
  102. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/test/gemm_testbed.py +557 -0
  103. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/test/profiler.py +70 -0
  104. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/type_hint.py +39 -0
  105. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/utils/__init__.py +1 -0
  106. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/utils/device.py +76 -0
  107. warp/native/cutlass/tools/library/scripts/pycutlass/src/pycutlass/utils/reference_model.py +255 -0
  108. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/__init__.py +0 -0
  109. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_dgrad_implicit_gemm_f16nhwc_f16nhwc_f16nhwc_tensor_op_f16_sm80.py +201 -0
  110. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_dgrad_implicit_gemm_f16nhwc_f16nhwc_f32nhwc_tensor_op_f32_sm80.py +177 -0
  111. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_dgrad_implicit_gemm_f32nhwc_f32nhwc_f32nhwc_simt_f32_sm80.py +98 -0
  112. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_dgrad_implicit_gemm_tf32nhwc_tf32nhwc_f32nhwc_tensor_op_f32_sm80.py +95 -0
  113. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_fprop_few_channels_f16nhwc_f16nhwc_f16nhwc_tensor_op_f32_sm80.py +163 -0
  114. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_fprop_fixed_channels_f16nhwc_f16nhwc_f16nhwc_tensor_op_f32_sm80.py +187 -0
  115. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_fprop_implicit_gemm_f16nhwc_f16nhwc_f16nhwc_tensor_op_f16_sm80.py +309 -0
  116. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_fprop_implicit_gemm_f16nhwc_f16nhwc_f32nhwc_tensor_op_f32_sm80.py +54 -0
  117. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_fprop_implicit_gemm_f32nhwc_f32nhwc_f32nhwc_simt_f32_sm80.py +96 -0
  118. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_fprop_implicit_gemm_tf32nhwc_tf32nhwc_f32nhwc_tensor_op_f32_sm80.py +107 -0
  119. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_strided_dgrad_implicit_gemm_f16nhwc_f16nhwc_f32nhwc_tensor_op_f32_sm80.py +253 -0
  120. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_wgrad_implicit_gemm_f16nhwc_f16nhwc_f16nhwc_tensor_op_f16_sm80.py +97 -0
  121. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_wgrad_implicit_gemm_f16nhwc_f16nhwc_f32nhwc_tensor_op_f32_sm80.py +242 -0
  122. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_wgrad_implicit_gemm_f32nhwc_f32nhwc_f32nhwc_simt_f32_sm80.py +96 -0
  123. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/conv2d_wgrad_implicit_gemm_tf32nhwc_tf32nhwc_f32nhwc_tensor_op_f32_sm80.py +107 -0
  124. warp/native/cutlass/tools/library/scripts/pycutlass/test/conv/run_all_tests.py +10 -0
  125. warp/native/cutlass/tools/library/scripts/pycutlass/test/frontend/test_frontend.py +146 -0
  126. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/__init__.py +0 -0
  127. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/gemm_bf16_sm80.py +96 -0
  128. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/gemm_f16_sm80.py +447 -0
  129. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/gemm_f32_sm80.py +146 -0
  130. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/gemm_f64_sm80.py +102 -0
  131. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/gemm_grouped_sm80.py +203 -0
  132. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/gemm_s8_sm80.py +229 -0
  133. warp/native/cutlass/tools/library/scripts/pycutlass/test/gemm/run_all_tests.py +9 -0
  134. warp/native/cutlass/tools/library/scripts/pycutlass/test/unit/test_sm80.py +453 -0
  135. warp/native/cutlass/tools/library/scripts/rank_2k_operation.py +398 -0
  136. warp/native/cutlass/tools/library/scripts/rank_k_operation.py +387 -0
  137. warp/native/cutlass/tools/library/scripts/rt.py +796 -0
  138. warp/native/cutlass/tools/library/scripts/symm_operation.py +400 -0
  139. warp/native/cutlass/tools/library/scripts/trmm_operation.py +407 -0
  140. warp/native/cutlass_gemm.cu +5 -3
  141. warp/native/exports.h +1240 -949
  142. warp/native/fabric.h +228 -0
  143. warp/native/hashgrid.cpp +4 -4
  144. warp/native/hashgrid.h +22 -2
  145. warp/native/initializer_array.h +2 -2
  146. warp/native/intersect.h +22 -7
  147. warp/native/intersect_adj.h +8 -8
  148. warp/native/intersect_tri.h +13 -16
  149. warp/native/marching.cu +157 -161
  150. warp/native/mat.h +119 -19
  151. warp/native/matnn.h +2 -2
  152. warp/native/mesh.cpp +108 -83
  153. warp/native/mesh.cu +243 -6
  154. warp/native/mesh.h +1547 -458
  155. warp/native/nanovdb/NanoVDB.h +1 -1
  156. warp/native/noise.h +272 -329
  157. warp/native/quat.h +51 -8
  158. warp/native/rand.h +45 -35
  159. warp/native/range.h +6 -2
  160. warp/native/reduce.cpp +157 -0
  161. warp/native/reduce.cu +348 -0
  162. warp/native/runlength_encode.cpp +62 -0
  163. warp/native/runlength_encode.cu +46 -0
  164. warp/native/scan.cu +11 -13
  165. warp/native/scan.h +1 -0
  166. warp/native/solid_angle.h +442 -0
  167. warp/native/sort.cpp +13 -0
  168. warp/native/sort.cu +9 -1
  169. warp/native/sparse.cpp +338 -0
  170. warp/native/sparse.cu +545 -0
  171. warp/native/spatial.h +2 -2
  172. warp/native/temp_buffer.h +30 -0
  173. warp/native/vec.h +126 -24
  174. warp/native/volume.h +120 -0
  175. warp/native/warp.cpp +658 -53
  176. warp/native/warp.cu +660 -68
  177. warp/native/warp.h +112 -12
  178. warp/optim/__init__.py +1 -0
  179. warp/optim/linear.py +922 -0
  180. warp/optim/sgd.py +92 -0
  181. warp/render/render_opengl.py +392 -152
  182. warp/render/render_usd.py +11 -11
  183. warp/sim/__init__.py +2 -2
  184. warp/sim/articulation.py +385 -185
  185. warp/sim/collide.py +21 -8
  186. warp/sim/import_mjcf.py +297 -106
  187. warp/sim/import_urdf.py +389 -210
  188. warp/sim/import_usd.py +198 -97
  189. warp/sim/inertia.py +17 -18
  190. warp/sim/integrator_euler.py +14 -8
  191. warp/sim/integrator_xpbd.py +161 -19
  192. warp/sim/model.py +795 -291
  193. warp/sim/optimizer.py +2 -6
  194. warp/sim/render.py +65 -3
  195. warp/sim/utils.py +3 -0
  196. warp/sparse.py +1227 -0
  197. warp/stubs.py +665 -223
  198. warp/tape.py +66 -15
  199. warp/tests/__main__.py +3 -6
  200. warp/tests/assets/curlnoise_golden.npy +0 -0
  201. warp/tests/assets/pnoise_golden.npy +0 -0
  202. warp/tests/assets/torus.usda +105 -105
  203. warp/tests/{test_class_kernel.py → aux_test_class_kernel.py} +9 -1
  204. warp/tests/aux_test_conditional_unequal_types_kernels.py +21 -0
  205. warp/tests/{test_dependent.py → aux_test_dependent.py} +2 -2
  206. warp/tests/{test_reference.py → aux_test_reference.py} +1 -1
  207. warp/tests/aux_test_unresolved_func.py +14 -0
  208. warp/tests/aux_test_unresolved_symbol.py +14 -0
  209. warp/tests/disabled_kinematics.py +239 -0
  210. warp/tests/run_coverage_serial.py +31 -0
  211. warp/tests/test_adam.py +103 -106
  212. warp/tests/test_arithmetic.py +128 -74
  213. warp/tests/test_array.py +1497 -211
  214. warp/tests/test_array_reduce.py +150 -0
  215. warp/tests/test_atomic.py +64 -28
  216. warp/tests/test_bool.py +99 -0
  217. warp/tests/test_builtins_resolution.py +1292 -0
  218. warp/tests/test_bvh.py +75 -43
  219. warp/tests/test_closest_point_edge_edge.py +54 -57
  220. warp/tests/test_codegen.py +233 -128
  221. warp/tests/test_compile_consts.py +28 -20
  222. warp/tests/test_conditional.py +108 -24
  223. warp/tests/test_copy.py +10 -12
  224. warp/tests/test_ctypes.py +112 -88
  225. warp/tests/test_dense.py +21 -14
  226. warp/tests/test_devices.py +98 -0
  227. warp/tests/test_dlpack.py +136 -108
  228. warp/tests/test_examples.py +277 -0
  229. warp/tests/test_fabricarray.py +955 -0
  230. warp/tests/test_fast_math.py +15 -11
  231. warp/tests/test_fem.py +1271 -0
  232. warp/tests/test_fp16.py +53 -19
  233. warp/tests/test_func.py +187 -74
  234. warp/tests/test_generics.py +194 -49
  235. warp/tests/test_grad.py +180 -116
  236. warp/tests/test_grad_customs.py +176 -0
  237. warp/tests/test_hash_grid.py +52 -37
  238. warp/tests/test_import.py +10 -23
  239. warp/tests/test_indexedarray.py +577 -24
  240. warp/tests/test_intersect.py +18 -9
  241. warp/tests/test_large.py +141 -0
  242. warp/tests/test_launch.py +251 -15
  243. warp/tests/test_lerp.py +64 -65
  244. warp/tests/test_linear_solvers.py +154 -0
  245. warp/tests/test_lvalue.py +493 -0
  246. warp/tests/test_marching_cubes.py +12 -13
  247. warp/tests/test_mat.py +508 -2778
  248. warp/tests/test_mat_lite.py +115 -0
  249. warp/tests/test_mat_scalar_ops.py +2889 -0
  250. warp/tests/test_math.py +103 -9
  251. warp/tests/test_matmul.py +305 -69
  252. warp/tests/test_matmul_lite.py +410 -0
  253. warp/tests/test_mesh.py +71 -14
  254. warp/tests/test_mesh_query_aabb.py +41 -25
  255. warp/tests/test_mesh_query_point.py +325 -34
  256. warp/tests/test_mesh_query_ray.py +39 -22
  257. warp/tests/test_mlp.py +30 -22
  258. warp/tests/test_model.py +92 -89
  259. warp/tests/test_modules_lite.py +39 -0
  260. warp/tests/test_multigpu.py +88 -114
  261. warp/tests/test_noise.py +12 -11
  262. warp/tests/test_operators.py +16 -20
  263. warp/tests/test_options.py +11 -11
  264. warp/tests/test_pinned.py +17 -18
  265. warp/tests/test_print.py +32 -11
  266. warp/tests/test_quat.py +275 -129
  267. warp/tests/test_rand.py +18 -16
  268. warp/tests/test_reload.py +38 -34
  269. warp/tests/test_rounding.py +50 -43
  270. warp/tests/test_runlength_encode.py +190 -0
  271. warp/tests/test_smoothstep.py +9 -11
  272. warp/tests/test_snippet.py +143 -0
  273. warp/tests/test_sparse.py +460 -0
  274. warp/tests/test_spatial.py +276 -243
  275. warp/tests/test_streams.py +110 -85
  276. warp/tests/test_struct.py +331 -85
  277. warp/tests/test_tape.py +39 -21
  278. warp/tests/test_torch.py +118 -89
  279. warp/tests/test_transient_module.py +12 -13
  280. warp/tests/test_types.py +614 -0
  281. warp/tests/test_utils.py +494 -0
  282. warp/tests/test_vec.py +354 -1987
  283. warp/tests/test_vec_lite.py +73 -0
  284. warp/tests/test_vec_scalar_ops.py +2099 -0
  285. warp/tests/test_volume.py +457 -293
  286. warp/tests/test_volume_write.py +124 -134
  287. warp/tests/unittest_serial.py +35 -0
  288. warp/tests/unittest_suites.py +341 -0
  289. warp/tests/unittest_utils.py +568 -0
  290. warp/tests/unused_test_misc.py +71 -0
  291. warp/tests/{test_debug.py → walkthough_debug.py} +3 -17
  292. warp/thirdparty/appdirs.py +36 -45
  293. warp/thirdparty/unittest_parallel.py +549 -0
  294. warp/torch.py +72 -30
  295. warp/types.py +1744 -713
  296. warp/utils.py +360 -350
  297. warp_lang-0.11.0.dist-info/LICENSE.md +36 -0
  298. warp_lang-0.11.0.dist-info/METADATA +238 -0
  299. warp_lang-0.11.0.dist-info/RECORD +332 -0
  300. {warp_lang-0.9.0.dist-info → warp_lang-0.11.0.dist-info}/WHEEL +1 -1
  301. warp/bin/warp-clang.exp +0 -0
  302. warp/bin/warp-clang.lib +0 -0
  303. warp/bin/warp.exp +0 -0
  304. warp/bin/warp.lib +0 -0
  305. warp/tests/test_all.py +0 -215
  306. warp/tests/test_array_scan.py +0 -60
  307. warp/tests/test_base.py +0 -208
  308. warp/tests/test_unresolved_func.py +0 -7
  309. warp/tests/test_unresolved_symbol.py +0 -7
  310. warp_lang-0.9.0.dist-info/METADATA +0 -20
  311. warp_lang-0.9.0.dist-info/RECORD +0 -177
  312. /warp/tests/{test_compile_consts_dummy.py → aux_test_compile_consts_dummy.py} +0 -0
  313. /warp/tests/{test_reference_reference.py → aux_test_reference_reference.py} +0 -0
  314. /warp/tests/{test_square.py → aux_test_square.py} +0 -0
  315. {warp_lang-0.9.0.dist-info → warp_lang-0.11.0.dist-info}/top_level.txt +0 -0
warp/tests/test_fem.py ADDED
@@ -0,0 +1,1271 @@
1
+ # Copyright (c) 2023 NVIDIA CORPORATION. All rights reserved.
2
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
3
+ # and proprietary rights in and to this software, related documentation
4
+ # and any modifications thereto. Any use, reproduction, disclosure or
5
+ # distribution of this software and related documentation without an express
6
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
7
+
8
+ import math
9
+ import unittest
10
+
11
+ import numpy as np
12
+ import warp as wp
13
+ from warp.tests.unittest_utils import *
14
+
15
+
16
+ from warp.fem import Field, Sample, Domain, Coords
17
+ from warp.fem import integrand, div, grad, curl, D, normal
18
+ import warp.fem as fem
19
+
20
+ from warp.fem.types import make_free_sample
21
+ from warp.fem.geometry.closest_point import project_on_tri_at_origin, project_on_tet_at_origin
22
+ from warp.fem.geometry import DeformedGeometry
23
+ from warp.fem.space import shape
24
+ from warp.fem.cache import dynamic_kernel
25
+ from warp.fem.utils import grid_to_tets, grid_to_tris, grid_to_quads, grid_to_hexes
26
+
27
+ wp.init()
28
+
29
+
30
+ @integrand
31
+ def linear_form(s: Sample, u: Field):
32
+ return u(s)
33
+
34
+
35
+ def test_integrate_gradient(test_case, device):
36
+ with wp.ScopedDevice(device):
37
+ # Grid geometry
38
+ geo = fem.Grid2D(res=wp.vec2i(5))
39
+
40
+ # Domain and function spaces
41
+ domain = fem.Cells(geometry=geo)
42
+ quadrature = fem.RegularQuadrature(domain=domain, order=3)
43
+
44
+ scalar_space = fem.make_polynomial_space(geo, degree=3)
45
+
46
+ u = scalar_space.make_field()
47
+ u.dof_values = wp.zeros_like(u.dof_values, requires_grad=True)
48
+
49
+ result = wp.empty(dtype=wp.float64, shape=(1), requires_grad=True)
50
+
51
+ tape = wp.Tape()
52
+
53
+ # forward pass
54
+ with tape:
55
+ fem.integrate(linear_form, quadrature=quadrature, fields={"u": u}, output=result)
56
+
57
+ tape.backward(result)
58
+
59
+ test = fem.make_test(space=scalar_space, domain=domain)
60
+ rhs = fem.integrate(linear_form, quadrature=quadrature, fields={"u": test})
61
+
62
+ err = np.linalg.norm(rhs.numpy() - u.dof_values.grad.numpy())
63
+ test_case.assertLess(err, 1.0e-8)
64
+
65
+
66
+ @fem.integrand
67
+ def bilinear_field(s: fem.Sample, domain: fem.Domain):
68
+ x = domain(s)
69
+ return x[0] * x[1]
70
+
71
+
72
+ @fem.integrand
73
+ def grad_field(s: fem.Sample, p: fem.Field):
74
+ return fem.grad(p, s)
75
+
76
+
77
+ def test_interpolate_gradient(test_case, device):
78
+ with wp.ScopedDevice(device):
79
+ # Quad mesh with single element
80
+ # so we can test gradient with respect to vertex positions
81
+ 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)
82
+ quads = wp.array([[0, 2, 3, 1]], dtype=int)
83
+ geo = fem.Quadmesh2D(quads, positions)
84
+
85
+ # Quadratic scalar space
86
+ scalar_space = fem.make_polynomial_space(geo, degree=2)
87
+
88
+ # Point-based vector space
89
+ # So we can test gradient with respect to inteprolation point position
90
+ point_coords = wp.array([[[0.5, 0.5, 0.0]]], dtype=fem.Coords, requires_grad=True)
91
+ interpolation_nodes = fem.PointBasisSpace(
92
+ fem.ExplicitQuadrature(domain=fem.Cells(geo), points=point_coords, weights=wp.array([[1.0]], dtype=float))
93
+ )
94
+ vector_space = fem.make_collocated_function_space(interpolation_nodes, dtype=wp.vec2)
95
+
96
+ # Initialize scalar field with known function
97
+ scalar_field = scalar_space.make_field()
98
+ scalar_field.dof_values.requires_grad = True
99
+ fem.interpolate(bilinear_field, dest=scalar_field)
100
+
101
+ # Interpolate gradient at center point
102
+ vector_field = vector_space.make_field()
103
+ vector_field.dof_values.requires_grad = True
104
+ tape = wp.Tape()
105
+ with tape:
106
+ fem.interpolate(grad_field, dest=vector_field, fields={"p": scalar_field})
107
+
108
+ assert_np_equal(vector_field.dof_values.numpy(), np.array([[1.0, 1.0]]))
109
+
110
+ vector_field.dof_values.grad.assign([1.0, 0.0])
111
+ tape.backward()
112
+
113
+ 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]))
114
+ assert_np_equal(
115
+ geo.positions.grad.numpy(),
116
+ np.array(
117
+ [
118
+ [0.25, 0.25],
119
+ [0.25, 0.25],
120
+ [-0.25, -0.25],
121
+ [-0.25, -0.25],
122
+ ]
123
+ ),
124
+ )
125
+ assert_np_equal(point_coords.grad.numpy(), np.array([[[0.0, 2.0, 0.0]]]))
126
+
127
+ tape.zero()
128
+ scalar_field.dof_values.grad.zero_()
129
+ geo.positions.grad.zero_()
130
+ point_coords.grad.zero_()
131
+
132
+ vector_field.dof_values.grad.assign([0.0, 1.0])
133
+ tape.backward()
134
+
135
+ assert_np_equal(scalar_field.dof_values.grad.numpy(), np.array([0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.5, 0.0, 0.0]))
136
+ assert_np_equal(
137
+ geo.positions.grad.numpy(),
138
+ np.array(
139
+ [
140
+ [0.25, 0.25],
141
+ [-0.25, -0.25],
142
+ [0.25, 0.25],
143
+ [-0.25, -0.25],
144
+ ]
145
+ ),
146
+ )
147
+ assert_np_equal(point_coords.grad.numpy(), np.array([[[2.0, 0.0, 0.0]]]))
148
+
149
+ @integrand
150
+ def vector_divergence_form(s: Sample, u: Field, q: Field):
151
+ return div(u, s) * q(s)
152
+
153
+
154
+ @integrand
155
+ def vector_grad_form(s: Sample, u: Field, q: Field):
156
+ return wp.dot(u(s), grad(q, s))
157
+
158
+
159
+ @integrand
160
+ def vector_boundary_form(domain: Domain, s: Sample, u: Field, q: Field):
161
+ return wp.dot(u(s) * q(s), normal(domain, s))
162
+
163
+
164
+ def test_vector_divergence_theorem(test_case, device):
165
+ rng = np.random.default_rng(123)
166
+
167
+ with wp.ScopedDevice(device):
168
+ # Grid geometry
169
+ geo = fem.Grid2D(res=wp.vec2i(5))
170
+
171
+ # Domain and function spaces
172
+ interior = fem.Cells(geometry=geo)
173
+ boundary = fem.BoundarySides(geometry=geo)
174
+
175
+ vector_space = fem.make_polynomial_space(geo, degree=2, dtype=wp.vec2)
176
+ scalar_space = fem.make_polynomial_space(geo, degree=1, dtype=float)
177
+
178
+ u = vector_space.make_field()
179
+ u.dof_values = rng.random(size=(u.dof_values.shape[0], 2))
180
+
181
+ # Divergence theorem
182
+ constant_one = scalar_space.make_field()
183
+ constant_one.dof_values.fill_(1.0)
184
+
185
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=vector_space.degree)
186
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=vector_space.degree)
187
+ div_int = fem.integrate(
188
+ vector_divergence_form,
189
+ quadrature=interior_quadrature,
190
+ fields={"u": u, "q": constant_one},
191
+ kernel_options={"enable_backward": False},
192
+ )
193
+ boundary_int = fem.integrate(
194
+ vector_boundary_form,
195
+ quadrature=boundary_quadrature,
196
+ fields={"u": u.trace(), "q": constant_one.trace()},
197
+ kernel_options={"enable_backward": False},
198
+ )
199
+
200
+ test_case.assertAlmostEqual(div_int, boundary_int, places=5)
201
+
202
+ # Integration by parts
203
+ q = scalar_space.make_field()
204
+ q.dof_values = rng.random(size=q.dof_values.shape[0])
205
+
206
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=vector_space.degree + scalar_space.degree)
207
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=vector_space.degree + scalar_space.degree)
208
+ div_int = fem.integrate(
209
+ vector_divergence_form,
210
+ quadrature=interior_quadrature,
211
+ fields={"u": u, "q": q},
212
+ kernel_options={"enable_backward": False},
213
+ )
214
+ grad_int = fem.integrate(
215
+ vector_grad_form,
216
+ quadrature=interior_quadrature,
217
+ fields={"u": u, "q": q},
218
+ kernel_options={"enable_backward": False},
219
+ )
220
+ boundary_int = fem.integrate(
221
+ vector_boundary_form,
222
+ quadrature=boundary_quadrature,
223
+ fields={"u": u.trace(), "q": q.trace()},
224
+ kernel_options={"enable_backward": False},
225
+ )
226
+
227
+ test_case.assertAlmostEqual(div_int + grad_int, boundary_int, places=5)
228
+
229
+
230
+ @integrand
231
+ def tensor_divergence_form(s: Sample, tau: Field, v: Field):
232
+ return wp.dot(div(tau, s), v(s))
233
+
234
+
235
+ @integrand
236
+ def tensor_grad_form(s: Sample, tau: Field, v: Field):
237
+ return wp.ddot(wp.transpose(tau(s)), grad(v, s))
238
+
239
+
240
+ @integrand
241
+ def tensor_boundary_form(domain: Domain, s: Sample, tau: Field, v: Field):
242
+ return wp.dot(tau(s) * v(s), normal(domain, s))
243
+
244
+
245
+ def test_tensor_divergence_theorem(test_case, device):
246
+ rng = np.random.default_rng(123)
247
+
248
+ with wp.ScopedDevice(device):
249
+ # Grid geometry
250
+ geo = fem.Grid2D(res=wp.vec2i(5))
251
+
252
+ # Domain and function spaces
253
+ interior = fem.Cells(geometry=geo)
254
+ boundary = fem.BoundarySides(geometry=geo)
255
+
256
+ tensor_space = fem.make_polynomial_space(geo, degree=2, dtype=wp.mat22)
257
+ vector_space = fem.make_polynomial_space(geo, degree=1, dtype=wp.vec2)
258
+
259
+ tau = tensor_space.make_field()
260
+ tau.dof_values = rng.random(size=(tau.dof_values.shape[0], 2, 2))
261
+
262
+ # Divergence theorem
263
+ constant_vec = vector_space.make_field()
264
+ constant_vec.dof_values.fill_(wp.vec2(0.5, 2.0))
265
+
266
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=tensor_space.degree)
267
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=tensor_space.degree)
268
+ div_int = fem.integrate(
269
+ tensor_divergence_form,
270
+ quadrature=interior_quadrature,
271
+ fields={"tau": tau, "v": constant_vec},
272
+ kernel_options={"enable_backward": False},
273
+ )
274
+ boundary_int = fem.integrate(
275
+ tensor_boundary_form,
276
+ quadrature=boundary_quadrature,
277
+ fields={"tau": tau.trace(), "v": constant_vec.trace()},
278
+ kernel_options={"enable_backward": False},
279
+ )
280
+
281
+ test_case.assertAlmostEqual(div_int, boundary_int, places=5)
282
+
283
+ # Integration by parts
284
+ v = vector_space.make_field()
285
+ v.dof_values = rng.random(size=(v.dof_values.shape[0], 2))
286
+
287
+ interior_quadrature = fem.RegularQuadrature(domain=interior, order=tensor_space.degree + vector_space.degree)
288
+ boundary_quadrature = fem.RegularQuadrature(domain=boundary, order=tensor_space.degree + vector_space.degree)
289
+ div_int = fem.integrate(
290
+ tensor_divergence_form,
291
+ quadrature=interior_quadrature,
292
+ fields={"tau": tau, "v": v},
293
+ kernel_options={"enable_backward": False},
294
+ )
295
+ grad_int = fem.integrate(
296
+ tensor_grad_form,
297
+ quadrature=interior_quadrature,
298
+ fields={"tau": tau, "v": v},
299
+ kernel_options={"enable_backward": False},
300
+ )
301
+ boundary_int = fem.integrate(
302
+ tensor_boundary_form,
303
+ quadrature=boundary_quadrature,
304
+ fields={"tau": tau.trace(), "v": v.trace()},
305
+ kernel_options={"enable_backward": False},
306
+ )
307
+
308
+ test_case.assertAlmostEqual(div_int + grad_int, boundary_int, places=5)
309
+
310
+
311
+ @integrand
312
+ def grad_decomposition(s: Sample, u: Field, v: Field):
313
+ return wp.length_sq(grad(u, s) * v(s) - D(u, s) * v(s) - wp.cross(curl(u, s), v(s)))
314
+
315
+
316
+ def test_grad_decomposition(test_case, device):
317
+ rng = np.random.default_rng(123)
318
+
319
+ with wp.ScopedDevice(device):
320
+ # Grid geometry
321
+ geo = fem.Grid3D(res=wp.vec3i(5))
322
+
323
+ # Domain and function spaces
324
+ domain = fem.Cells(geometry=geo)
325
+ quadrature = fem.RegularQuadrature(domain=domain, order=4)
326
+
327
+ vector_space = fem.make_polynomial_space(geo, degree=2, dtype=wp.vec3)
328
+ u = vector_space.make_field()
329
+
330
+ u.dof_values = rng.random(size=(u.dof_values.shape[0], 3))
331
+
332
+ err = fem.integrate(grad_decomposition, quadrature=quadrature, fields={"u": u, "v": u})
333
+ test_case.assertLess(err, 1.0e-8)
334
+
335
+
336
+ def _gen_trimesh(N):
337
+ x = np.linspace(0.0, 1.0, N + 1)
338
+ y = np.linspace(0.0, 1.0, N + 1)
339
+
340
+ positions = np.transpose(np.meshgrid(x, y, indexing="ij")).reshape(-1, 2)
341
+
342
+ vidx = grid_to_tris(N, N)
343
+
344
+ return wp.array(positions, dtype=wp.vec2), wp.array(vidx, dtype=int)
345
+
346
+
347
+ def _gen_quadmesh(N):
348
+ x = np.linspace(0.0, 1.0, N + 1)
349
+ y = np.linspace(0.0, 1.0, N + 1)
350
+
351
+ positions = np.transpose(np.meshgrid(x, y, indexing="ij")).reshape(-1, 2)
352
+
353
+ vidx = grid_to_quads(N, N)
354
+
355
+ return wp.array(positions, dtype=wp.vec2), wp.array(vidx, dtype=int)
356
+
357
+
358
+ def _gen_tetmesh(N):
359
+ x = np.linspace(0.0, 1.0, N + 1)
360
+ y = np.linspace(0.0, 1.0, N + 1)
361
+ z = np.linspace(0.0, 1.0, N + 1)
362
+
363
+ positions = np.transpose(np.meshgrid(x, y, z, indexing="ij")).reshape(-1, 3)
364
+
365
+ vidx = grid_to_tets(N, N, N)
366
+
367
+ return wp.array(positions, dtype=wp.vec3), wp.array(vidx, dtype=int)
368
+
369
+
370
+ def _gen_hexmesh(N):
371
+ x = np.linspace(0.0, 1.0, N + 1)
372
+ y = np.linspace(0.0, 1.0, N + 1)
373
+ z = np.linspace(0.0, 1.0, N + 1)
374
+
375
+ positions = np.transpose(np.meshgrid(x, y, z, indexing="ij")).reshape(-1, 3)
376
+
377
+ vidx = grid_to_hexes(N, N, N)
378
+
379
+ return wp.array(positions, dtype=wp.vec3), wp.array(vidx, dtype=int)
380
+
381
+
382
+ def _launch_test_geometry_kernel(geo: fem.Geometry, device):
383
+ @dynamic_kernel(suffix=geo.name, kernel_options={"enable_backward": False})
384
+ def test_geo_cells_kernel(
385
+ cell_arg: geo.CellArg,
386
+ qps: wp.array(dtype=Coords),
387
+ qp_weights: wp.array(dtype=float),
388
+ cell_measures: wp.array(dtype=float),
389
+ ):
390
+ cell_index, q = wp.tid()
391
+
392
+ coords = qps[q]
393
+ s = make_free_sample(cell_index, coords)
394
+
395
+ wp.atomic_add(cell_measures, cell_index, geo.cell_measure(cell_arg, s) * qp_weights[q])
396
+
397
+ REF_MEASURE = geo.reference_side().measure()
398
+
399
+ @dynamic_kernel(suffix=geo.name, kernel_options={"enable_backward": False, "max_unroll": 1})
400
+ def test_geo_sides_kernel(
401
+ side_arg: geo.SideArg,
402
+ qps: wp.array(dtype=Coords),
403
+ qp_weights: wp.array(dtype=float),
404
+ side_measures: wp.array(dtype=float),
405
+ ):
406
+ side_index, q = wp.tid()
407
+
408
+ coords = qps[q]
409
+ s = make_free_sample(side_index, coords)
410
+
411
+ cell_arg = geo.side_to_cell_arg(side_arg)
412
+ inner_cell_index = geo.side_inner_cell_index(side_arg, side_index)
413
+ outer_cell_index = geo.side_outer_cell_index(side_arg, side_index)
414
+ inner_cell_coords = geo.side_inner_cell_coords(side_arg, side_index, coords)
415
+ outer_cell_coords = geo.side_outer_cell_coords(side_arg, side_index, coords)
416
+
417
+ inner_s = make_free_sample(inner_cell_index, inner_cell_coords)
418
+ outer_s = make_free_sample(outer_cell_index, outer_cell_coords)
419
+
420
+ pos_side = geo.side_position(side_arg, s)
421
+ pos_inner = geo.cell_position(cell_arg, inner_s)
422
+ pos_outer = geo.cell_position(cell_arg, outer_s)
423
+
424
+ for k in range(type(pos_side).length):
425
+ wp.expect_near(pos_side[k], pos_inner[k], 0.0001)
426
+ wp.expect_near(pos_side[k], pos_outer[k], 0.0001)
427
+
428
+ inner_side_coords = geo.side_from_cell_coords(side_arg, side_index, inner_cell_index, inner_cell_coords)
429
+ outer_side_coords = geo.side_from_cell_coords(side_arg, side_index, outer_cell_index, outer_cell_coords)
430
+
431
+ wp.expect_near(coords, inner_side_coords, 0.0001)
432
+ wp.expect_near(coords, outer_side_coords, 0.0001)
433
+
434
+ vol = geo.side_measure(side_arg, s)
435
+ wp.atomic_add(side_measures, side_index, vol * qp_weights[q])
436
+
437
+ # test consistency of side normal, measure, and deformation gradient
438
+ F = geo.side_deformation_gradient(side_arg, s)
439
+ F_det = DeformedGeometry._side_measure(F)
440
+ wp.expect_near(F_det * REF_MEASURE, vol)
441
+
442
+ nor = geo.side_normal(side_arg, s)
443
+ F_cross = DeformedGeometry._side_normal(F)
444
+
445
+ for k in range(type(pos_side).length):
446
+ wp.expect_near(F_cross[k], nor[k], 0.0001)
447
+
448
+ cell_measures = wp.zeros(dtype=float, device=device, shape=geo.cell_count())
449
+
450
+ cell_quadrature = fem.RegularQuadrature(fem.Cells(geo), order=2)
451
+ cell_qps = wp.array(cell_quadrature.points, dtype=Coords, device=device)
452
+ cell_qp_weights = wp.array(cell_quadrature.weights, dtype=float, device=device)
453
+
454
+ wp.launch(
455
+ kernel=test_geo_cells_kernel,
456
+ dim=(geo.cell_count(), cell_qps.shape[0]),
457
+ inputs=[geo.cell_arg_value(device), cell_qps, cell_qp_weights, cell_measures],
458
+ device=device,
459
+ )
460
+
461
+ side_measures = wp.zeros(dtype=float, device=device, shape=geo.side_count())
462
+
463
+ side_quadrature = fem.RegularQuadrature(fem.Sides(geo), order=2)
464
+ side_qps = wp.array(side_quadrature.points, dtype=Coords, device=device)
465
+ side_qp_weights = wp.array(side_quadrature.weights, dtype=float, device=device)
466
+
467
+ wp.launch(
468
+ kernel=test_geo_sides_kernel,
469
+ dim=(geo.side_count(), side_qps.shape[0]),
470
+ inputs=[geo.side_arg_value(device), side_qps, side_qp_weights, side_measures],
471
+ device=device,
472
+ )
473
+
474
+ return side_measures, cell_measures
475
+
476
+
477
+ def test_grid_2d(test_case, device):
478
+ N = 3
479
+
480
+ geo = fem.Grid2D(res=wp.vec2i(N))
481
+
482
+ test_case.assertEqual(geo.cell_count(), N**2)
483
+ test_case.assertEqual(geo.vertex_count(), (N + 1) ** 2)
484
+ test_case.assertEqual(geo.side_count(), 2 * (N + 1) * N)
485
+ test_case.assertEqual(geo.boundary_side_count(), 4 * N)
486
+
487
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
488
+
489
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N)), tol=1.0e-4)
490
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
491
+
492
+
493
+ def test_triangle_mesh(test_case, device):
494
+ N = 3
495
+
496
+ with wp.ScopedDevice(device):
497
+ positions, tri_vidx = _gen_trimesh(N)
498
+
499
+ geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=positions)
500
+
501
+ test_case.assertEqual(geo.cell_count(), 2 * (N) ** 2)
502
+ test_case.assertEqual(geo.vertex_count(), (N + 1) ** 2)
503
+ test_case.assertEqual(geo.side_count(), 2 * (N + 1) * N + (N**2))
504
+ test_case.assertEqual(geo.boundary_side_count(), 4 * N)
505
+
506
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
507
+
508
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 0.5 / (N**2)), tol=1.0e-4)
509
+ test_case.assertAlmostEqual(np.sum(side_measures.numpy()), 2 * (N + 1) + N * math.sqrt(2.0), places=4)
510
+
511
+
512
+ def test_quad_mesh(test_case, device):
513
+ N = 3
514
+
515
+ with wp.ScopedDevice(device):
516
+ positions, quad_vidx = _gen_quadmesh(N)
517
+
518
+ geo = fem.Quadmesh2D(quad_vertex_indices=quad_vidx, positions=positions)
519
+
520
+ test_case.assertEqual(geo.cell_count(), N**2)
521
+ test_case.assertEqual(geo.vertex_count(), (N + 1) ** 2)
522
+ test_case.assertEqual(geo.side_count(), 2 * (N + 1) * N)
523
+ test_case.assertEqual(geo.boundary_side_count(), 4 * N)
524
+
525
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
526
+
527
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N)), tol=1.0e-4)
528
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
529
+
530
+
531
+ def test_grid_3d(test_case, device):
532
+ N = 3
533
+
534
+ geo = fem.Grid3D(res=wp.vec3i(N))
535
+
536
+ test_case.assertEqual(geo.cell_count(), (N) ** 3)
537
+ test_case.assertEqual(geo.vertex_count(), (N + 1) ** 3)
538
+ test_case.assertEqual(geo.side_count(), 3 * (N + 1) * N**2)
539
+ test_case.assertEqual(geo.boundary_side_count(), 6 * N * N)
540
+ test_case.assertEqual(geo.edge_count(), 3 * N * (N + 1) ** 2)
541
+
542
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
543
+
544
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
545
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**3)), tol=1.0e-4)
546
+
547
+
548
+ def test_tet_mesh(test_case, device):
549
+ N = 3
550
+
551
+ with wp.ScopedDevice(device):
552
+ positions, tet_vidx = _gen_tetmesh(N)
553
+
554
+ geo = fem.Tetmesh(tet_vertex_indices=tet_vidx, positions=positions)
555
+
556
+ test_case.assertEqual(geo.cell_count(), 5 * (N) ** 3)
557
+ test_case.assertEqual(geo.vertex_count(), (N + 1) ** 3)
558
+ test_case.assertEqual(geo.side_count(), 6 * (N + 1) * N**2 + (N**3) * 4)
559
+ test_case.assertEqual(geo.boundary_side_count(), 12 * N * N)
560
+ test_case.assertEqual(geo.edge_count(), 3 * N * (N + 1) * (2 * N + 1))
561
+
562
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
563
+
564
+ test_case.assertAlmostEqual(np.sum(cell_measures.numpy()), 1.0, places=4)
565
+ test_case.assertAlmostEqual(np.sum(side_measures.numpy()), 0.5 * 6 * (N + 1) + N * 2 * math.sqrt(3.0), places=4)
566
+
567
+
568
+ def test_hex_mesh(test_case, device):
569
+ N = 3
570
+
571
+ with wp.ScopedDevice(device):
572
+ positions, tet_vidx = _gen_hexmesh(N)
573
+
574
+ geo = fem.Hexmesh(hex_vertex_indices=tet_vidx, positions=positions)
575
+
576
+ test_case.assertEqual(geo.cell_count(), (N) ** 3)
577
+ test_case.assertEqual(geo.vertex_count(), (N + 1) ** 3)
578
+ test_case.assertEqual(geo.side_count(), 3 * (N + 1) * N**2)
579
+ test_case.assertEqual(geo.boundary_side_count(), 6 * N * N)
580
+ test_case.assertEqual(geo.edge_count(), 3 * N * (N + 1) ** 2)
581
+
582
+ side_measures, cell_measures = _launch_test_geometry_kernel(geo, device)
583
+
584
+ assert_np_equal(side_measures.numpy(), np.full(side_measures.shape, 1.0 / (N**2)), tol=1.0e-4)
585
+ assert_np_equal(cell_measures.numpy(), np.full(cell_measures.shape, 1.0 / (N**3)), tol=1.0e-4)
586
+
587
+
588
+ @integrand
589
+ def _rigid_deformation_field(s: Sample, domain: Domain, translation: wp.vec3, rotation: wp.vec3, scale: float):
590
+ q = wp.quat_from_axis_angle(wp.normalize(rotation), wp.length(rotation))
591
+ return translation + scale * wp.quat_rotate(q, domain(s)) - domain(s)
592
+
593
+
594
+ def test_deformed_geometry(test_case, device):
595
+ N = 3
596
+
597
+ with wp.ScopedDevice(device):
598
+ positions, tet_vidx = _gen_tetmesh(N)
599
+
600
+ geo = fem.Tetmesh(tet_vertex_indices=tet_vidx, positions=positions)
601
+
602
+ translation = [1.0, 2.0, 3.0]
603
+ rotation = [0.0, math.pi / 4.0, 0.0]
604
+ scale = 2.0
605
+
606
+ vector_space = fem.make_polynomial_space(geo, dtype=wp.vec3, degree=2)
607
+ pos_field = vector_space.make_field()
608
+ fem.interpolate(
609
+ _rigid_deformation_field,
610
+ dest=pos_field,
611
+ values={"translation": translation, "rotation": rotation, "scale": scale},
612
+ )
613
+
614
+ deformed_geo = pos_field.make_deformed_geometry()
615
+
616
+ # rigidly-deformed geometry
617
+
618
+ test_case.assertEqual(geo.cell_count(), 5 * (N) ** 3)
619
+ test_case.assertEqual(geo.vertex_count(), (N + 1) ** 3)
620
+ test_case.assertEqual(geo.side_count(), 6 * (N + 1) * N**2 + (N**3) * 4)
621
+ test_case.assertEqual(geo.boundary_side_count(), 12 * N * N)
622
+
623
+ side_measures, cell_measures = _launch_test_geometry_kernel(deformed_geo, device)
624
+
625
+ test_case.assertAlmostEqual(np.sum(cell_measures.numpy()), scale**3, places=4)
626
+ test_case.assertAlmostEqual(
627
+ np.sum(side_measures.numpy()), scale**2 * (0.5 * 6 * (N + 1) + N * 2 * math.sqrt(3.0)), places=4
628
+ )
629
+
630
+ @wp.kernel
631
+ def _test_deformed_geometry_normal(
632
+ geo_index_arg: geo.SideIndexArg, geo_arg: geo.SideArg, def_arg: deformed_geo.SideArg, rotation: wp.vec3
633
+ ):
634
+ i = wp.tid()
635
+ side_index = deformed_geo.boundary_side_index(geo_index_arg, i)
636
+
637
+ s = make_free_sample(side_index, Coords(0.5, 0.5, 0.0))
638
+ geo_n = geo.side_normal(geo_arg, s)
639
+ def_n = deformed_geo.side_normal(def_arg, s)
640
+
641
+ q = wp.quat_from_axis_angle(wp.normalize(rotation), wp.length(rotation))
642
+ wp.expect_near(wp.quat_rotate(q, geo_n), def_n, 0.001)
643
+
644
+ wp.launch(
645
+ _test_deformed_geometry_normal,
646
+ dim=geo.boundary_side_count(),
647
+ device=device,
648
+ inputs=[
649
+ geo.side_index_arg_value(device),
650
+ geo.side_arg_value(device),
651
+ deformed_geo.side_arg_value(device),
652
+ rotation,
653
+ ],
654
+ )
655
+
656
+ wp.synchronize()
657
+
658
+
659
+ @wp.kernel
660
+ def _test_closest_point_on_tri_kernel(
661
+ e0: wp.vec2,
662
+ e1: wp.vec2,
663
+ points: wp.array(dtype=wp.vec2),
664
+ sq_dist: wp.array(dtype=float),
665
+ coords: wp.array(dtype=Coords),
666
+ ):
667
+ i = wp.tid()
668
+ d2, c = project_on_tri_at_origin(points[i], e0, e1)
669
+ sq_dist[i] = d2
670
+ coords[i] = c
671
+
672
+
673
+ @wp.kernel
674
+ def _test_closest_point_on_tet_kernel(
675
+ e0: wp.vec3,
676
+ e1: wp.vec3,
677
+ e2: wp.vec3,
678
+ points: wp.array(dtype=wp.vec3),
679
+ sq_dist: wp.array(dtype=float),
680
+ coords: wp.array(dtype=Coords),
681
+ ):
682
+ i = wp.tid()
683
+ d2, c = project_on_tet_at_origin(points[i], e0, e1, e2)
684
+ sq_dist[i] = d2
685
+ coords[i] = c
686
+
687
+
688
+ def test_closest_point_queries(test_case, device):
689
+ # Test some simple lookup queries
690
+ e0 = wp.vec2(2.0, 0.0)
691
+ e1 = wp.vec2(0.0, 2.0)
692
+
693
+ points = wp.array(
694
+ (
695
+ [-1.0, -1.0],
696
+ [0.5, 0.5],
697
+ [1.0, 1.0],
698
+ [2.0, 2.0],
699
+ ),
700
+ dtype=wp.vec2,
701
+ device=device,
702
+ )
703
+ expected_sq_dist = np.array([2.0, 0.0, 0.0, 2.0])
704
+ 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]])
705
+
706
+ sq_dist = wp.empty(shape=points.shape, dtype=float, device=device)
707
+ coords = wp.empty(shape=points.shape, dtype=Coords, device=device)
708
+ wp.launch(
709
+ _test_closest_point_on_tri_kernel, dim=points.shape, device=device, inputs=[e0, e1, points, sq_dist, coords]
710
+ )
711
+
712
+ assert_np_equal(coords.numpy(), expected_coords)
713
+ assert_np_equal(sq_dist.numpy(), expected_sq_dist)
714
+
715
+ # Tet
716
+
717
+ e0 = wp.vec3(3.0, 0.0, 0.0)
718
+ e1 = wp.vec3(0.0, 3.0, 0.0)
719
+ e2 = wp.vec3(0.0, 0.0, 3.0)
720
+
721
+ points = wp.array(
722
+ (
723
+ [-1.0, -1.0, -1.0],
724
+ [0.5, 0.5, 0.5],
725
+ [1.0, 1.0, 1.0],
726
+ [2.0, 2.0, 2.0],
727
+ ),
728
+ dtype=wp.vec3,
729
+ device=device,
730
+ )
731
+ expected_sq_dist = np.array([3.0, 0.0, 0.0, 3.0])
732
+ expected_coords = np.array(
733
+ [
734
+ [0.0, 0.0, 0.0],
735
+ [1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0],
736
+ [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0],
737
+ [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0],
738
+ ]
739
+ )
740
+
741
+ sq_dist = wp.empty(shape=points.shape, dtype=float, device=device)
742
+ coords = wp.empty(shape=points.shape, dtype=Coords, device=device)
743
+ wp.launch(
744
+ _test_closest_point_on_tet_kernel, dim=points.shape, device=device, inputs=[e0, e1, e2, points, sq_dist, coords]
745
+ )
746
+
747
+ assert_np_equal(coords.numpy(), expected_coords, tol=1.0e-4)
748
+ assert_np_equal(sq_dist.numpy(), expected_sq_dist, tol=1.0e-4)
749
+
750
+
751
+ def test_regular_quadrature(test_case, device):
752
+ from warp.fem.geometry.element import LinearEdge, Triangle, Polynomial
753
+
754
+ for family in Polynomial:
755
+ # test integrating monomials
756
+ for degree in range(8):
757
+ coords, weights = LinearEdge().instantiate_quadrature(degree, family=family)
758
+ res = sum(w * pow(c[0], degree) for w, c in zip(weights, coords))
759
+ ref = 1.0 / (degree + 1)
760
+
761
+ test_case.assertAlmostEqual(ref, res, places=4)
762
+
763
+ # test integrating y^k1 (1 - x)^k2 on triangle using transformation to square
764
+ for x_degree in range(4):
765
+ for y_degree in range(4):
766
+ coords, weights = Triangle().instantiate_quadrature(x_degree + y_degree, family=family)
767
+ res = 0.5 * sum(w * pow(1.0 - c[1], x_degree) * pow(c[2], y_degree) for w, c in zip(weights, coords))
768
+
769
+ ref = 1.0 / ((x_degree + y_degree + 2) * (y_degree + 1))
770
+ # print(x_degree, y_degree, family, len(coords), res, ref)
771
+ test_case.assertAlmostEqual(ref, res, places=4)
772
+
773
+ # test integrating y^k1 (1 - x)^k2 on triangle using direct formulas
774
+ for x_degree in range(5):
775
+ for y_degree in range(5):
776
+ coords, weights = Triangle().instantiate_quadrature(x_degree + y_degree, family=None)
777
+ res = 0.5 * sum(w * pow(1.0 - c[1], x_degree) * pow(c[2], y_degree) for w, c in zip(weights, coords))
778
+
779
+ ref = 1.0 / ((x_degree + y_degree + 2) * (y_degree + 1))
780
+ test_case.assertAlmostEqual(ref, res, places=4)
781
+
782
+
783
+ def test_dof_mapper(test_case, device):
784
+ matrix_types = [wp.mat22, wp.mat33]
785
+
786
+ # Symmetric mapper
787
+ for mapping in fem.SymmetricTensorMapper.Mapping:
788
+ for dtype in matrix_types:
789
+ mapper = fem.SymmetricTensorMapper(dtype, mapping=mapping)
790
+ dof_dtype = mapper.dof_dtype
791
+
792
+ for k in range(dof_dtype._length_):
793
+ elem = np.array(dof_dtype(0.0))
794
+ elem[k] = 1.0
795
+ dof_vec = dof_dtype(elem)
796
+
797
+ mat = mapper.dof_to_value(dof_vec)
798
+ dof_round_trip = mapper.value_to_dof(mat)
799
+
800
+ # Check that value_to_dof(dof_to_value) is idempotent
801
+ assert_np_equal(np.array(dof_round_trip), np.array(dof_vec))
802
+
803
+ # Check that value is unitary for Frobenius norm 0.5 * |tau:tau|
804
+ frob_norm2 = 0.5 * wp.ddot(mat, mat)
805
+ test_case.assertAlmostEqual(frob_norm2, 1.0, places=6)
806
+
807
+ # Skew-symmetric mapper
808
+ for dtype in matrix_types:
809
+ mapper = fem.SkewSymmetricTensorMapper(dtype)
810
+ dof_dtype = mapper.dof_dtype
811
+
812
+ if hasattr(dof_dtype, "_length_"):
813
+ for k in range(dof_dtype._length_):
814
+ elem = np.array(dof_dtype(0.0))
815
+ elem[k] = 1.0
816
+ dof_vec = dof_dtype(elem)
817
+
818
+ mat = mapper.dof_to_value(dof_vec)
819
+ dof_round_trip = mapper.value_to_dof(mat)
820
+
821
+ # Check that value_to_dof(dof_to_value) is idempotent
822
+ assert_np_equal(np.array(dof_round_trip), np.array(dof_vec))
823
+
824
+ # Check that value is unitary for Frobenius norm 0.5 * |tau:tau|
825
+ frob_norm2 = 0.5 * wp.ddot(mat, mat)
826
+ test_case.assertAlmostEqual(frob_norm2, 1.0, places=6)
827
+ else:
828
+ dof_val = 1.0
829
+
830
+ mat = mapper.dof_to_value(dof_val)
831
+ dof_round_trip = mapper.value_to_dof(mat)
832
+
833
+ test_case.assertAlmostEqual(dof_round_trip, dof_val)
834
+
835
+ # Check that value is unitary for Frobenius norm 0.5 * |tau:tau|
836
+ frob_norm2 = 0.5 * wp.ddot(mat, mat)
837
+ test_case.assertAlmostEqual(frob_norm2, 1.0, places=6)
838
+
839
+
840
+ def test_shape_function_weight(test_case, shape: shape.ShapeFunction, coord_sampler, CENTER_COORDS):
841
+ NODE_COUNT = shape.NODES_PER_ELEMENT
842
+ weight_fn = shape.make_element_inner_weight()
843
+ node_coords_fn = shape.make_node_coords_in_element()
844
+
845
+ # Weight at node should be 1
846
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
847
+ def node_unity_test():
848
+ n = wp.tid()
849
+ node_w = weight_fn(node_coords_fn(n), n)
850
+ wp.expect_near(node_w, 1.0, places=5)
851
+
852
+ wp.launch(node_unity_test, dim=NODE_COUNT, inputs=[])
853
+
854
+ # Sum of node quadrature weights should be one (order 0)
855
+ # Sum of weighted quadrature coords should be element center (order 1)
856
+ node_quadrature_weight_fn = shape.make_node_quadrature_weight()
857
+
858
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
859
+ def node_quadrature_unity_test():
860
+ sum_node_qp = float(0.0)
861
+ sum_node_qp_coords = Coords(0.0)
862
+
863
+ for n in range(NODE_COUNT):
864
+ w = node_quadrature_weight_fn(n)
865
+ sum_node_qp += w
866
+ sum_node_qp_coords += w * node_coords_fn(n)
867
+
868
+ wp.expect_near(sum_node_qp, 1.0, 0.0001)
869
+ wp.expect_near(sum_node_qp_coords, CENTER_COORDS, 0.0001)
870
+
871
+ wp.launch(node_quadrature_unity_test, dim=1, inputs=[])
872
+
873
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
874
+ def partition_of_unity_test():
875
+ rng_state = wp.rand_init(4321, wp.tid())
876
+ coords = coord_sampler(rng_state)
877
+
878
+ # sum of node weights anywhere should be 1.0
879
+ w_sum = float(0.0)
880
+ for n in range(NODE_COUNT):
881
+ w_sum += weight_fn(coords, n)
882
+
883
+ wp.expect_near(w_sum, 1.0, 0.0001)
884
+
885
+ n_samples = 100
886
+ wp.launch(partition_of_unity_test, dim=n_samples, inputs=[])
887
+
888
+
889
+ def test_shape_function_trace(test_case, shape: shape.ShapeFunction, CENTER_COORDS):
890
+ NODE_COUNT = shape.NODES_PER_ELEMENT
891
+ node_coords_fn = shape.make_node_coords_in_element()
892
+
893
+ # Sum of node quadrature weights should be one (order 0)
894
+ # Sum of weighted quadrature coords should be element center (order 1)
895
+ trace_node_quadrature_weight_fn = shape.make_trace_node_quadrature_weight()
896
+
897
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
898
+ def trace_node_quadrature_unity_test():
899
+ sum_node_qp = float(0.0)
900
+ sum_node_qp_coords = Coords(0.0)
901
+
902
+ for n in range(NODE_COUNT):
903
+ coords = node_coords_fn(n)
904
+
905
+ if wp.abs(coords[0]) < 1.0e-6:
906
+ w = trace_node_quadrature_weight_fn(n)
907
+ sum_node_qp += w
908
+ sum_node_qp_coords += w * node_coords_fn(n)
909
+
910
+ wp.expect_near(sum_node_qp, 1.0, 0.0001)
911
+ wp.expect_near(sum_node_qp_coords, CENTER_COORDS, 0.0001)
912
+
913
+ wp.launch(trace_node_quadrature_unity_test, dim=1, inputs=[])
914
+
915
+
916
+ def test_shape_function_gradient(test_case, shape: shape.ShapeFunction, coord_sampler, coord_delta_sampler):
917
+ weight_fn = shape.make_element_inner_weight()
918
+ weight_gradient_fn = shape.make_element_inner_weight_gradient()
919
+
920
+ @dynamic_kernel(suffix=shape.name, kernel_options={"enable_backward": False})
921
+ def finite_difference_test():
922
+ i, n = wp.tid()
923
+ rng_state = wp.rand_init(1234, i)
924
+
925
+ coords = coord_sampler(rng_state)
926
+
927
+ epsilon = 0.003
928
+ param_delta, coords_delta = coord_delta_sampler(epsilon, rng_state)
929
+
930
+ w_p = weight_fn(coords + coords_delta, n)
931
+ w_m = weight_fn(coords - coords_delta, n)
932
+
933
+ gp = weight_gradient_fn(coords + coords_delta, n)
934
+ gm = weight_gradient_fn(coords - coords_delta, n)
935
+
936
+ # 2nd-order finite-difference test
937
+ # See Schroeder 2019, Practical course on computing derivatives in code
938
+ delta_ref = w_p - w_m
939
+ delta_est = wp.dot(gp + gm, param_delta)
940
+
941
+ # wp.printf("%d %f %f \n", n, delta_ref, delta_est)
942
+ wp.expect_near(delta_ref, delta_est, 0.0001)
943
+
944
+ n_samples = 100
945
+ wp.launch(finite_difference_test, dim=(n_samples, shape.NODES_PER_ELEMENT), inputs=[])
946
+
947
+
948
+ def test_square_shape_functions(test_case, device):
949
+ SQUARE_CENTER_COORDS = wp.constant(Coords(0.5, 0.5, 0.0))
950
+ SQUARE_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 0.5, 0.0))
951
+
952
+ @wp.func
953
+ def square_coord_sampler(state: wp.uint32):
954
+ return Coords(wp.randf(state), wp.randf(state), 0.0)
955
+
956
+ @wp.func
957
+ def square_coord_delta_sampler(epsilon: float, state: wp.uint32):
958
+ param_delta = wp.normalize(wp.vec2(wp.randf(state), wp.randf(state))) * epsilon
959
+ return param_delta, Coords(param_delta[0], param_delta[1], 0.0)
960
+
961
+ Q_1 = shape.SquareBipolynomialShapeFunctions(degree=1, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
962
+ Q_2 = shape.SquareBipolynomialShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
963
+ Q_3 = shape.SquareBipolynomialShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
964
+
965
+ test_shape_function_weight(test_case, Q_1, square_coord_sampler, SQUARE_CENTER_COORDS)
966
+ test_shape_function_weight(test_case, Q_2, square_coord_sampler, SQUARE_CENTER_COORDS)
967
+ test_shape_function_weight(test_case, Q_3, square_coord_sampler, SQUARE_CENTER_COORDS)
968
+ test_shape_function_trace(test_case, Q_1, SQUARE_SIDE_CENTER_COORDS)
969
+ test_shape_function_trace(test_case, Q_2, SQUARE_SIDE_CENTER_COORDS)
970
+ test_shape_function_trace(test_case, Q_3, SQUARE_SIDE_CENTER_COORDS)
971
+ test_shape_function_gradient(test_case, Q_1, square_coord_sampler, square_coord_delta_sampler)
972
+ test_shape_function_gradient(test_case, Q_2, square_coord_sampler, square_coord_delta_sampler)
973
+ test_shape_function_gradient(test_case, Q_3, square_coord_sampler, square_coord_delta_sampler)
974
+
975
+ Q_1 = shape.SquareBipolynomialShapeFunctions(degree=1, family=fem.Polynomial.GAUSS_LEGENDRE)
976
+ Q_2 = shape.SquareBipolynomialShapeFunctions(degree=2, family=fem.Polynomial.GAUSS_LEGENDRE)
977
+ Q_3 = shape.SquareBipolynomialShapeFunctions(degree=3, family=fem.Polynomial.GAUSS_LEGENDRE)
978
+
979
+ test_shape_function_weight(test_case, Q_1, square_coord_sampler, SQUARE_CENTER_COORDS)
980
+ test_shape_function_weight(test_case, Q_2, square_coord_sampler, SQUARE_CENTER_COORDS)
981
+ test_shape_function_weight(test_case, Q_3, square_coord_sampler, SQUARE_CENTER_COORDS)
982
+ test_shape_function_gradient(test_case, Q_1, square_coord_sampler, square_coord_delta_sampler)
983
+ test_shape_function_gradient(test_case, Q_2, square_coord_sampler, square_coord_delta_sampler)
984
+ test_shape_function_gradient(test_case, Q_3, square_coord_sampler, square_coord_delta_sampler)
985
+
986
+ S_2 = shape.SquareSerendipityShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
987
+ S_3 = shape.SquareSerendipityShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
988
+
989
+ test_shape_function_weight(test_case, S_2, square_coord_sampler, SQUARE_CENTER_COORDS)
990
+ test_shape_function_weight(test_case, S_3, square_coord_sampler, SQUARE_CENTER_COORDS)
991
+ test_shape_function_trace(test_case, S_2, SQUARE_SIDE_CENTER_COORDS)
992
+ test_shape_function_trace(test_case, S_3, SQUARE_SIDE_CENTER_COORDS)
993
+ test_shape_function_gradient(test_case, S_2, square_coord_sampler, square_coord_delta_sampler)
994
+ test_shape_function_gradient(test_case, S_3, square_coord_sampler, square_coord_delta_sampler)
995
+
996
+ P_c1 = shape.SquareNonConformingPolynomialShapeFunctions(degree=1)
997
+ P_c2 = shape.SquareNonConformingPolynomialShapeFunctions(degree=2)
998
+ P_c3 = shape.SquareNonConformingPolynomialShapeFunctions(degree=3)
999
+
1000
+ test_shape_function_weight(test_case, P_c1, square_coord_sampler, SQUARE_CENTER_COORDS)
1001
+ test_shape_function_weight(test_case, P_c2, square_coord_sampler, SQUARE_CENTER_COORDS)
1002
+ test_shape_function_weight(test_case, P_c3, square_coord_sampler, SQUARE_CENTER_COORDS)
1003
+ test_shape_function_gradient(test_case, P_c1, square_coord_sampler, square_coord_delta_sampler)
1004
+ test_shape_function_gradient(test_case, P_c2, square_coord_sampler, square_coord_delta_sampler)
1005
+ test_shape_function_gradient(test_case, P_c3, square_coord_sampler, square_coord_delta_sampler)
1006
+
1007
+ wp.synchronize()
1008
+
1009
+
1010
+ def test_cube_shape_functions(test_case, device):
1011
+ CUBE_CENTER_COORDS = wp.constant(Coords(0.5, 0.5, 0.5))
1012
+ CUBE_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 0.5, 0.5))
1013
+
1014
+ @wp.func
1015
+ def cube_coord_sampler(state: wp.uint32):
1016
+ return Coords(wp.randf(state), wp.randf(state), wp.randf(state))
1017
+
1018
+ @wp.func
1019
+ def cube_coord_delta_sampler(epsilon: float, state: wp.uint32):
1020
+ param_delta = wp.normalize(wp.vec3(wp.randf(state), wp.randf(state), wp.randf(state))) * epsilon
1021
+ return param_delta, param_delta
1022
+
1023
+ Q_1 = shape.CubeTripolynomialShapeFunctions(degree=1, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1024
+ Q_2 = shape.CubeTripolynomialShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1025
+ Q_3 = shape.CubeTripolynomialShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1026
+
1027
+ test_shape_function_weight(test_case, Q_1, cube_coord_sampler, CUBE_CENTER_COORDS)
1028
+ test_shape_function_weight(test_case, Q_2, cube_coord_sampler, CUBE_CENTER_COORDS)
1029
+ test_shape_function_weight(test_case, Q_3, cube_coord_sampler, CUBE_CENTER_COORDS)
1030
+ test_shape_function_trace(test_case, Q_1, CUBE_SIDE_CENTER_COORDS)
1031
+ test_shape_function_trace(test_case, Q_2, CUBE_SIDE_CENTER_COORDS)
1032
+ test_shape_function_trace(test_case, Q_3, CUBE_SIDE_CENTER_COORDS)
1033
+ test_shape_function_gradient(test_case, Q_1, cube_coord_sampler, cube_coord_delta_sampler)
1034
+ test_shape_function_gradient(test_case, Q_2, cube_coord_sampler, cube_coord_delta_sampler)
1035
+ test_shape_function_gradient(test_case, Q_3, cube_coord_sampler, cube_coord_delta_sampler)
1036
+
1037
+ Q_1 = shape.CubeTripolynomialShapeFunctions(degree=1, family=fem.Polynomial.GAUSS_LEGENDRE)
1038
+ Q_2 = shape.CubeTripolynomialShapeFunctions(degree=2, family=fem.Polynomial.GAUSS_LEGENDRE)
1039
+ Q_3 = shape.CubeTripolynomialShapeFunctions(degree=3, family=fem.Polynomial.GAUSS_LEGENDRE)
1040
+
1041
+ test_shape_function_weight(test_case, Q_1, cube_coord_sampler, CUBE_CENTER_COORDS)
1042
+ test_shape_function_weight(test_case, Q_2, cube_coord_sampler, CUBE_CENTER_COORDS)
1043
+ test_shape_function_weight(test_case, Q_3, cube_coord_sampler, CUBE_CENTER_COORDS)
1044
+ test_shape_function_gradient(test_case, Q_1, cube_coord_sampler, cube_coord_delta_sampler)
1045
+ test_shape_function_gradient(test_case, Q_2, cube_coord_sampler, cube_coord_delta_sampler)
1046
+ test_shape_function_gradient(test_case, Q_3, cube_coord_sampler, cube_coord_delta_sampler)
1047
+
1048
+ S_2 = shape.CubeSerendipityShapeFunctions(degree=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1049
+ S_3 = shape.CubeSerendipityShapeFunctions(degree=3, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1050
+
1051
+ test_shape_function_weight(test_case, S_2, cube_coord_sampler, CUBE_CENTER_COORDS)
1052
+ test_shape_function_weight(test_case, S_3, cube_coord_sampler, CUBE_CENTER_COORDS)
1053
+ test_shape_function_trace(test_case, S_2, CUBE_SIDE_CENTER_COORDS)
1054
+ test_shape_function_trace(test_case, S_3, CUBE_SIDE_CENTER_COORDS)
1055
+ test_shape_function_gradient(test_case, S_2, cube_coord_sampler, cube_coord_delta_sampler)
1056
+ test_shape_function_gradient(test_case, S_3, cube_coord_sampler, cube_coord_delta_sampler)
1057
+
1058
+ P_c1 = shape.CubeNonConformingPolynomialShapeFunctions(degree=1)
1059
+ P_c2 = shape.CubeNonConformingPolynomialShapeFunctions(degree=2)
1060
+ P_c3 = shape.CubeNonConformingPolynomialShapeFunctions(degree=3)
1061
+
1062
+ test_shape_function_weight(test_case, P_c1, cube_coord_sampler, CUBE_CENTER_COORDS)
1063
+ test_shape_function_weight(test_case, P_c2, cube_coord_sampler, CUBE_CENTER_COORDS)
1064
+ test_shape_function_weight(test_case, P_c3, cube_coord_sampler, CUBE_CENTER_COORDS)
1065
+ test_shape_function_gradient(test_case, P_c1, cube_coord_sampler, cube_coord_delta_sampler)
1066
+ test_shape_function_gradient(test_case, P_c2, cube_coord_sampler, cube_coord_delta_sampler)
1067
+ test_shape_function_gradient(test_case, P_c3, cube_coord_sampler, cube_coord_delta_sampler)
1068
+
1069
+ wp.synchronize()
1070
+
1071
+
1072
+ def test_tri_shape_functions(test_case, device):
1073
+ TRI_CENTER_COORDS = wp.constant(Coords(1 / 3.0, 1 / 3.0, 1 / 3.0))
1074
+ TRI_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 0.5, 0.5))
1075
+
1076
+ @wp.func
1077
+ def tri_coord_sampler(state: wp.uint32):
1078
+ a = wp.randf(state)
1079
+ b = wp.randf(state)
1080
+ return Coords(1.0 - a - b, a, b)
1081
+
1082
+ @wp.func
1083
+ def tri_coord_delta_sampler(epsilon: float, state: wp.uint32):
1084
+ param_delta = wp.normalize(wp.vec2(wp.randf(state), wp.randf(state))) * epsilon
1085
+ a = param_delta[0]
1086
+ b = param_delta[1]
1087
+ return param_delta, Coords(-a - b, a, b)
1088
+
1089
+ P_1 = shape.Triangle2DPolynomialShapeFunctions(degree=1)
1090
+ P_2 = shape.Triangle2DPolynomialShapeFunctions(degree=2)
1091
+ P_3 = shape.Triangle2DPolynomialShapeFunctions(degree=3)
1092
+
1093
+ test_shape_function_weight(test_case, P_1, tri_coord_sampler, TRI_CENTER_COORDS)
1094
+ test_shape_function_weight(test_case, P_2, tri_coord_sampler, TRI_CENTER_COORDS)
1095
+ test_shape_function_weight(test_case, P_3, tri_coord_sampler, TRI_CENTER_COORDS)
1096
+ test_shape_function_trace(test_case, P_1, TRI_SIDE_CENTER_COORDS)
1097
+ test_shape_function_trace(test_case, P_2, TRI_SIDE_CENTER_COORDS)
1098
+ test_shape_function_trace(test_case, P_3, TRI_SIDE_CENTER_COORDS)
1099
+ test_shape_function_gradient(test_case, P_1, tri_coord_sampler, tri_coord_delta_sampler)
1100
+ test_shape_function_gradient(test_case, P_2, tri_coord_sampler, tri_coord_delta_sampler)
1101
+ test_shape_function_gradient(test_case, P_3, tri_coord_sampler, tri_coord_delta_sampler)
1102
+
1103
+ P_1d = shape.Triangle2DNonConformingPolynomialShapeFunctions(degree=1)
1104
+ P_2d = shape.Triangle2DNonConformingPolynomialShapeFunctions(degree=2)
1105
+ P_3d = shape.Triangle2DNonConformingPolynomialShapeFunctions(degree=3)
1106
+
1107
+ test_shape_function_weight(test_case, P_1d, tri_coord_sampler, TRI_CENTER_COORDS)
1108
+ test_shape_function_weight(test_case, P_2d, tri_coord_sampler, TRI_CENTER_COORDS)
1109
+ test_shape_function_weight(test_case, P_3d, tri_coord_sampler, TRI_CENTER_COORDS)
1110
+ test_shape_function_gradient(test_case, P_1d, tri_coord_sampler, tri_coord_delta_sampler)
1111
+ test_shape_function_gradient(test_case, P_2d, tri_coord_sampler, tri_coord_delta_sampler)
1112
+ test_shape_function_gradient(test_case, P_3d, tri_coord_sampler, tri_coord_delta_sampler)
1113
+
1114
+ wp.synchronize()
1115
+
1116
+
1117
+ def test_tet_shape_functions(test_case, device):
1118
+ TET_CENTER_COORDS = wp.constant(Coords(1 / 4.0, 1 / 4.0, 1 / 4.0))
1119
+ TET_SIDE_CENTER_COORDS = wp.constant(Coords(0.0, 1.0 / 3.0, 1.0 / 3.0))
1120
+
1121
+ @wp.func
1122
+ def tet_coord_sampler(state: wp.uint32):
1123
+ return Coords(wp.randf(state), wp.randf(state), wp.randf(state))
1124
+
1125
+ @wp.func
1126
+ def tet_coord_delta_sampler(epsilon: float, state: wp.uint32):
1127
+ param_delta = wp.normalize(wp.vec3(wp.randf(state), wp.randf(state), wp.randf(state))) * epsilon
1128
+ return param_delta, param_delta
1129
+
1130
+ P_1 = shape.TetrahedronPolynomialShapeFunctions(degree=1)
1131
+ P_2 = shape.TetrahedronPolynomialShapeFunctions(degree=2)
1132
+ P_3 = shape.TetrahedronPolynomialShapeFunctions(degree=3)
1133
+
1134
+ test_shape_function_weight(test_case, P_1, tet_coord_sampler, TET_CENTER_COORDS)
1135
+ test_shape_function_weight(test_case, P_2, tet_coord_sampler, TET_CENTER_COORDS)
1136
+ test_shape_function_weight(test_case, P_3, tet_coord_sampler, TET_CENTER_COORDS)
1137
+ test_shape_function_trace(test_case, P_1, TET_SIDE_CENTER_COORDS)
1138
+ test_shape_function_trace(test_case, P_2, TET_SIDE_CENTER_COORDS)
1139
+ test_shape_function_trace(test_case, P_3, TET_SIDE_CENTER_COORDS)
1140
+ test_shape_function_gradient(test_case, P_1, tet_coord_sampler, tet_coord_delta_sampler)
1141
+ test_shape_function_gradient(test_case, P_2, tet_coord_sampler, tet_coord_delta_sampler)
1142
+ test_shape_function_gradient(test_case, P_3, tet_coord_sampler, tet_coord_delta_sampler)
1143
+
1144
+ P_1d = shape.TetrahedronNonConformingPolynomialShapeFunctions(degree=1)
1145
+ P_2d = shape.TetrahedronNonConformingPolynomialShapeFunctions(degree=2)
1146
+ P_3d = shape.TetrahedronNonConformingPolynomialShapeFunctions(degree=3)
1147
+
1148
+ test_shape_function_weight(test_case, P_1d, tet_coord_sampler, TET_CENTER_COORDS)
1149
+ test_shape_function_weight(test_case, P_2d, tet_coord_sampler, TET_CENTER_COORDS)
1150
+ test_shape_function_weight(test_case, P_3d, tet_coord_sampler, TET_CENTER_COORDS)
1151
+ test_shape_function_gradient(test_case, P_1d, tet_coord_sampler, tet_coord_delta_sampler)
1152
+ test_shape_function_gradient(test_case, P_2d, tet_coord_sampler, tet_coord_delta_sampler)
1153
+ test_shape_function_gradient(test_case, P_3d, tet_coord_sampler, tet_coord_delta_sampler)
1154
+
1155
+ wp.synchronize()
1156
+
1157
+
1158
+ def test_point_basis(test_case, device):
1159
+ geo = fem.Grid2D(res=wp.vec2i(2))
1160
+
1161
+ domain = fem.Cells(geo)
1162
+
1163
+ quadrature = fem.RegularQuadrature(domain, order=2, family=fem.Polynomial.GAUSS_LEGENDRE)
1164
+ point_basis = fem.PointBasisSpace(quadrature)
1165
+
1166
+ point_space = fem.make_collocated_function_space(point_basis)
1167
+ point_test = fem.make_test(point_space, domain=domain)
1168
+
1169
+ # Sample at particle positions
1170
+ ones = fem.integrate(linear_form, fields={"u": point_test}, nodal=True)
1171
+ test_case.assertAlmostEqual(np.sum(ones.numpy()), 1.0, places=5)
1172
+
1173
+ # Sampling outside of particle positions
1174
+ other_quadrature = fem.RegularQuadrature(domain, order=2, family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE)
1175
+ zeros = fem.integrate(linear_form, quadrature=other_quadrature, fields={"u": point_test})
1176
+
1177
+ test_case.assertAlmostEqual(np.sum(zeros.numpy()), 0.0, places=5)
1178
+
1179
+
1180
+ @fem.integrand
1181
+ def _bicubic(s: Sample, domain: Domain):
1182
+ x = domain(s)
1183
+ return wp.pow(x[0], 3.0) * wp.pow(x[1], 3.0)
1184
+
1185
+
1186
+ @fem.integrand
1187
+ def _piecewise_constant(s: Sample):
1188
+ return float(s.element_index)
1189
+
1190
+
1191
+ def test_particle_quadratures(test_case, device):
1192
+ geo = fem.Grid2D(res=wp.vec2i(2))
1193
+
1194
+ domain = fem.Cells(geo)
1195
+ points, weights = domain.reference_element().instantiate_quadrature(order=4, family=fem.Polynomial.GAUSS_LEGENDRE)
1196
+ points_per_cell = len(points)
1197
+
1198
+ points = points * domain.element_count()
1199
+ weights = weights * domain.element_count()
1200
+
1201
+ points = wp.array(points, shape=(domain.element_count(), points_per_cell), dtype=Coords, device=device)
1202
+ weights = wp.array(weights, shape=(domain.element_count(), points_per_cell), dtype=float, device=device)
1203
+
1204
+ explicit_quadrature = fem.ExplicitQuadrature(domain, points, weights)
1205
+
1206
+ test_case.assertEqual(explicit_quadrature.points_per_element(), points_per_cell)
1207
+ test_case.assertEqual(explicit_quadrature.total_point_count(), points_per_cell * geo.cell_count())
1208
+
1209
+ val = fem.integrate(_bicubic, quadrature=explicit_quadrature)
1210
+ test_case.assertAlmostEqual(val, 1.0 / 16, places=5)
1211
+
1212
+ element_indices = wp.array([3, 3, 2], dtype=int, device=device)
1213
+ element_coords = wp.array(
1214
+ [
1215
+ [0.25, 0.5, 0.0],
1216
+ [0.5, 0.25, 0.0],
1217
+ [0.5, 0.5, 0.0],
1218
+ ],
1219
+ dtype=Coords,
1220
+ device=device,
1221
+ )
1222
+
1223
+ pic_quadrature = fem.PicQuadrature(domain, positions=(element_indices, element_coords))
1224
+
1225
+ test_case.assertIsNone(pic_quadrature.points_per_element())
1226
+ test_case.assertEqual(pic_quadrature.total_point_count(), 3)
1227
+ test_case.assertEqual(pic_quadrature.active_cell_count(), 2)
1228
+
1229
+ val = fem.integrate(_piecewise_constant, quadrature=pic_quadrature)
1230
+ test_case.assertAlmostEqual(val, 1.25, places=5)
1231
+
1232
+
1233
+ devices = get_test_devices()
1234
+
1235
+
1236
+ class TestFem(unittest.TestCase):
1237
+ pass
1238
+
1239
+
1240
+ add_function_test(TestFem, "test_regular_quadrature", test_regular_quadrature)
1241
+ add_function_test(TestFem, "test_closest_point_queries", test_closest_point_queries)
1242
+ add_function_test(TestFem, "test_grad_decomposition", test_grad_decomposition, devices=devices)
1243
+ add_function_test(TestFem, "test_integrate_gradient", test_integrate_gradient, devices=devices)
1244
+ add_function_test(TestFem, "test_interpolate_gradient", test_interpolate_gradient, devices=devices)
1245
+ add_function_test(TestFem, "test_vector_divergence_theorem", test_vector_divergence_theorem, devices=devices)
1246
+ add_function_test(TestFem, "test_tensor_divergence_theorem", test_tensor_divergence_theorem, devices=devices)
1247
+ add_function_test(TestFem, "test_grid_2d", test_grid_2d, devices=devices)
1248
+ add_function_test(TestFem, "test_triangle_mesh", test_triangle_mesh, devices=devices)
1249
+ add_function_test(TestFem, "test_quad_mesh", test_quad_mesh, devices=devices)
1250
+ add_function_test(TestFem, "test_grid_3d", test_grid_3d, devices=devices)
1251
+ add_function_test(TestFem, "test_tet_mesh", test_tet_mesh, devices=devices)
1252
+ add_function_test(TestFem, "test_hex_mesh", test_hex_mesh, devices=devices)
1253
+ add_function_test(TestFem, "test_deformed_geometry", test_deformed_geometry, devices=devices)
1254
+ add_function_test(TestFem, "test_dof_mapper", test_dof_mapper)
1255
+ add_function_test(TestFem, "test_point_basis", test_point_basis)
1256
+ add_function_test(TestFem, "test_particle_quadratures", test_particle_quadratures)
1257
+
1258
+
1259
+ class TestFemShapeFunctions(unittest.TestCase):
1260
+ pass
1261
+
1262
+
1263
+ add_function_test(TestFemShapeFunctions, "test_square_shape_functions", test_square_shape_functions)
1264
+ add_function_test(TestFemShapeFunctions, "test_cube_shape_functions", test_cube_shape_functions)
1265
+ add_function_test(TestFemShapeFunctions, "test_tri_shape_functions", test_tri_shape_functions)
1266
+ add_function_test(TestFemShapeFunctions, "test_tet_shape_functions", test_tet_shape_functions)
1267
+
1268
+
1269
+ if __name__ == "__main__":
1270
+ wp.build.clear_kernel_cache()
1271
+ unittest.main(verbosity=2)