warp-lang 1.0.0b2__py3-none-win_amd64.whl → 1.0.0b6__py3-none-win_amd64.whl

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

Potentially problematic release.


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

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