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