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
@@ -0,0 +1,738 @@
1
+ import math
2
+
3
+ import warp as wp
4
+ import numpy as np
5
+
6
+ from warp.fem.geometry import Grid3D
7
+ from warp.fem.polynomial import Polynomial, quadrature_1d, lagrange_scales, is_closed
8
+ from warp.fem.types import Coords
9
+ from warp.fem import cache
10
+
11
+ from .tet_shape_function import TetrahedronPolynomialShapeFunctions
12
+
13
+ _CUBE_EDGE_INDICES = wp.constant(
14
+ wp.mat(shape=(3, 4), dtype=int)(
15
+ [
16
+ [0, 4, 2, 6],
17
+ [3, 1, 7, 5],
18
+ [8, 11, 9, 10],
19
+ ]
20
+ )
21
+ )
22
+
23
+
24
+ class CubeTripolynomialShapeFunctions:
25
+ VERTEX = 0
26
+ EDGE = 1
27
+ FACE = 2
28
+ INTERIOR = 3
29
+
30
+ def __init__(self, degree: int, family: Polynomial):
31
+ self.family = family
32
+
33
+ self.ORDER = wp.constant(degree)
34
+ self.NODES_PER_ELEMENT = wp.constant((degree + 1) ** 3)
35
+ self.NODES_PER_EDGE = wp.constant(degree + 1)
36
+
37
+ lobatto_coords, lobatto_weight = quadrature_1d(point_count=degree + 1, family=family)
38
+ lagrange_scale = lagrange_scales(lobatto_coords)
39
+
40
+ NodeVec = wp.types.vector(length=degree + 1, dtype=wp.float32)
41
+ self.LOBATTO_COORDS = wp.constant(NodeVec(lobatto_coords))
42
+ self.LOBATTO_WEIGHT = wp.constant(NodeVec(lobatto_weight))
43
+ self.LAGRANGE_SCALE = wp.constant(NodeVec(lagrange_scale))
44
+ self.ORDER_PLUS_ONE = wp.constant(self.ORDER + 1)
45
+
46
+ self._node_ijk = self._make_node_ijk()
47
+ self.node_type_and_type_index = self._make_node_type_and_type_index()
48
+
49
+ @property
50
+ def name(self) -> str:
51
+ return f"Cube_Q{self.ORDER}_{self.family}"
52
+
53
+ @wp.func
54
+ def _vertex_coords_f(vidx_in_cell: int):
55
+ x = vidx_in_cell // 4
56
+ y = (vidx_in_cell - 4 * x) // 2
57
+ z = vidx_in_cell - 4 * x - 2 * y
58
+ return wp.vec3(float(x), float(y), float(z))
59
+
60
+ def _make_node_ijk(self):
61
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
62
+
63
+ def node_ijk(
64
+ node_index_in_elt: int,
65
+ ):
66
+ node_i = node_index_in_elt // (ORDER_PLUS_ONE * ORDER_PLUS_ONE)
67
+ node_jk = node_index_in_elt - ORDER_PLUS_ONE * ORDER_PLUS_ONE * node_i
68
+ node_j = node_jk // ORDER_PLUS_ONE
69
+ node_k = node_jk - ORDER_PLUS_ONE * node_j
70
+ return node_i, node_j, node_k
71
+
72
+ return cache.get_func(node_ijk, self.name)
73
+
74
+ def _make_node_type_and_type_index(self):
75
+ ORDER = self.ORDER
76
+
77
+ @cache.dynamic_func(suffix=self.name)
78
+ def node_type_and_type_index(
79
+ node_index_in_elt: int,
80
+ ):
81
+ i, j, k = self._node_ijk(node_index_in_elt)
82
+
83
+ zi = wp.select(i == 0, 0, 1)
84
+ zj = wp.select(j == 0, 0, 1)
85
+ zk = wp.select(k == 0, 0, 1)
86
+
87
+ mi = wp.select(i == ORDER, 0, 1)
88
+ mj = wp.select(j == ORDER, 0, 1)
89
+ mk = wp.select(k == ORDER, 0, 1)
90
+
91
+ if zi + mi == 1:
92
+ if zj + mj == 1:
93
+ if zk + mk == 1:
94
+ # vertex
95
+ type_instance = mi * 4 + mj * 2 + mk
96
+ return CubeTripolynomialShapeFunctions.VERTEX, type_instance, 0
97
+
98
+ # z edge
99
+ type_instance = _CUBE_EDGE_INDICES[2, mi * 2 + mj]
100
+ type_index = k - 1
101
+ return CubeTripolynomialShapeFunctions.EDGE, type_instance, type_index
102
+
103
+ if zk + mk == 1:
104
+ # y edge
105
+ type_instance = _CUBE_EDGE_INDICES[1, mk * 2 + mi]
106
+ type_index = j - 1
107
+ return CubeTripolynomialShapeFunctions.EDGE, type_instance, type_index
108
+
109
+ # x face
110
+ type_instance = mi
111
+ type_index = wp.select(mi == 1, (j - 1) * (ORDER - 1) + k - 1, (k - 1) * (ORDER - 1) + j - 1)
112
+ return CubeTripolynomialShapeFunctions.FACE, type_instance, type_index
113
+
114
+ if zj + mj == 1:
115
+ if zk + mk == 1:
116
+ # x edge
117
+ type_instance = _CUBE_EDGE_INDICES[0, mj * 2 + mk]
118
+ type_index = i - 1
119
+ return CubeTripolynomialShapeFunctions.EDGE, type_instance, type_index
120
+
121
+ # y face
122
+ type_instance = 2 + mj
123
+ type_index = wp.select(mj == 1, (i - 1) * (ORDER - 1) + k - 1, (k - 1) * (ORDER - 1) + i - 1)
124
+ return CubeTripolynomialShapeFunctions.FACE, type_instance, type_index
125
+
126
+ if zk + mk == 1:
127
+ # z face
128
+ type_instance = 4 + mk
129
+ type_index = wp.select(mk == 1, (j - 1) * (ORDER - 1) + i - 1, (i - 1) * (ORDER - 1) + j - 1)
130
+ return CubeTripolynomialShapeFunctions.FACE, type_instance, type_index
131
+
132
+ type_index = ((i - 1) * (ORDER - 1) + (j - 1)) * (ORDER - 1) + k - 1
133
+ return CubeTripolynomialShapeFunctions.INTERIOR, 0, type_index
134
+
135
+ return node_type_and_type_index
136
+
137
+ def make_node_coords_in_element(self):
138
+ LOBATTO_COORDS = self.LOBATTO_COORDS
139
+
140
+ @cache.dynamic_func(suffix=self.name)
141
+ def node_coords_in_element(
142
+ node_index_in_elt: int,
143
+ ):
144
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
145
+ return Coords(LOBATTO_COORDS[node_i], LOBATTO_COORDS[node_j], LOBATTO_COORDS[node_k])
146
+
147
+ return node_coords_in_element
148
+
149
+ def make_node_quadrature_weight(self):
150
+ ORDER = self.ORDER
151
+ LOBATTO_WEIGHT = self.LOBATTO_WEIGHT
152
+
153
+ def node_quadrature_weight(
154
+ node_index_in_elt: int,
155
+ ):
156
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
157
+ return LOBATTO_WEIGHT[node_i] * LOBATTO_WEIGHT[node_j] * LOBATTO_WEIGHT[node_k]
158
+
159
+ def node_quadrature_weight_linear(
160
+ node_index_in_elt: int,
161
+ ):
162
+ return 0.125
163
+
164
+ if ORDER == 1:
165
+ return cache.get_func(node_quadrature_weight_linear, self.name)
166
+
167
+ return cache.get_func(node_quadrature_weight, self.name)
168
+
169
+ def make_trace_node_quadrature_weight(self):
170
+ ORDER = self.ORDER
171
+ LOBATTO_WEIGHT = self.LOBATTO_WEIGHT
172
+
173
+ def trace_node_quadrature_weight(
174
+ node_index_in_elt: int,
175
+ ):
176
+ # We're either on a side interior or at a vertex
177
+ # If we find one index at extremum, pick the two other
178
+
179
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
180
+
181
+ if node_i == 0 or node_i == ORDER:
182
+ return LOBATTO_WEIGHT[node_j] * LOBATTO_WEIGHT[node_k]
183
+
184
+ if node_j == 0 or node_j == ORDER:
185
+ return LOBATTO_WEIGHT[node_i] * LOBATTO_WEIGHT[node_k]
186
+
187
+ return LOBATTO_WEIGHT[node_i] * LOBATTO_WEIGHT[node_j]
188
+
189
+ def trace_node_quadrature_weight_linear(
190
+ node_index_in_elt: int,
191
+ ):
192
+ return 0.25
193
+
194
+ def trace_node_quadrature_weight_open(
195
+ node_index_in_elt: int,
196
+ ):
197
+ return 0.0
198
+
199
+ if not is_closed(self.family):
200
+ return cache.get_func(trace_node_quadrature_weight_open, self.name)
201
+
202
+ if ORDER == 1:
203
+ return cache.get_func(trace_node_quadrature_weight_linear, self.name)
204
+
205
+ return cache.get_func(trace_node_quadrature_weight, self.name)
206
+
207
+ def make_element_inner_weight(self):
208
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
209
+ LOBATTO_COORDS = self.LOBATTO_COORDS
210
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
211
+
212
+ def element_inner_weight(
213
+ coords: Coords,
214
+ node_index_in_elt: int,
215
+ ):
216
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
217
+
218
+ w = float(1.0)
219
+ for k in range(ORDER_PLUS_ONE):
220
+ if k != node_i:
221
+ w *= coords[0] - LOBATTO_COORDS[k]
222
+ if k != node_j:
223
+ w *= coords[1] - LOBATTO_COORDS[k]
224
+ if k != node_k:
225
+ w *= coords[2] - LOBATTO_COORDS[k]
226
+
227
+ w *= LAGRANGE_SCALE[node_i] * LAGRANGE_SCALE[node_j] * LAGRANGE_SCALE[node_k]
228
+
229
+ return w
230
+
231
+ def element_inner_weight_linear(
232
+ coords: Coords,
233
+ node_index_in_elt: int,
234
+ ):
235
+ v = CubeTripolynomialShapeFunctions._vertex_coords_f(node_index_in_elt)
236
+
237
+ wx = (1.0 - coords[0]) * (1.0 - v[0]) + v[0] * coords[0]
238
+ wy = (1.0 - coords[1]) * (1.0 - v[1]) + v[1] * coords[1]
239
+ wz = (1.0 - coords[2]) * (1.0 - v[2]) + v[2] * coords[2]
240
+ return wx * wy * wz
241
+
242
+ if self.ORDER == 1 and is_closed(self.family):
243
+ return cache.get_func(element_inner_weight_linear, self.name)
244
+
245
+ return cache.get_func(element_inner_weight, self.name)
246
+
247
+ def make_element_inner_weight_gradient(self):
248
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
249
+ LOBATTO_COORDS = self.LOBATTO_COORDS
250
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
251
+
252
+ def element_inner_weight_gradient(
253
+ coords: Coords,
254
+ node_index_in_elt: int,
255
+ ):
256
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
257
+
258
+ prefix_xy = float(1.0)
259
+ prefix_yz = float(1.0)
260
+ prefix_zx = float(1.0)
261
+ for k in range(ORDER_PLUS_ONE):
262
+ if k != node_i:
263
+ prefix_yz *= coords[0] - LOBATTO_COORDS[k]
264
+ if k != node_j:
265
+ prefix_zx *= coords[1] - LOBATTO_COORDS[k]
266
+ if k != node_k:
267
+ prefix_xy *= coords[2] - LOBATTO_COORDS[k]
268
+
269
+ prefix_x = prefix_zx * prefix_xy
270
+ prefix_y = prefix_yz * prefix_xy
271
+ prefix_z = prefix_zx * prefix_yz
272
+
273
+ grad_x = float(0.0)
274
+ grad_y = float(0.0)
275
+ grad_z = float(0.0)
276
+
277
+ for k in range(ORDER_PLUS_ONE):
278
+ if k != node_i:
279
+ delta_x = coords[0] - LOBATTO_COORDS[k]
280
+ grad_x = grad_x * delta_x + prefix_x
281
+ prefix_x *= delta_x
282
+ if k != node_j:
283
+ delta_y = coords[1] - LOBATTO_COORDS[k]
284
+ grad_y = grad_y * delta_y + prefix_y
285
+ prefix_y *= delta_y
286
+ if k != node_k:
287
+ delta_z = coords[2] - LOBATTO_COORDS[k]
288
+ grad_z = grad_z * delta_z + prefix_z
289
+ prefix_z *= delta_z
290
+
291
+ grad = (
292
+ LAGRANGE_SCALE[node_i]
293
+ * LAGRANGE_SCALE[node_j]
294
+ * LAGRANGE_SCALE[node_k]
295
+ * wp.vec3(
296
+ grad_x,
297
+ grad_y,
298
+ grad_z,
299
+ )
300
+ )
301
+
302
+ return grad
303
+
304
+ def element_inner_weight_gradient_linear(
305
+ coords: Coords,
306
+ node_index_in_elt: int,
307
+ ):
308
+ v = CubeTripolynomialShapeFunctions._vertex_coords_f(node_index_in_elt)
309
+
310
+ wx = (1.0 - coords[0]) * (1.0 - v[0]) + v[0] * coords[0]
311
+ wy = (1.0 - coords[1]) * (1.0 - v[1]) + v[1] * coords[1]
312
+ wz = (1.0 - coords[2]) * (1.0 - v[2]) + v[2] * coords[2]
313
+
314
+ dx = 2.0 * v[0] - 1.0
315
+ dy = 2.0 * v[1] - 1.0
316
+ dz = 2.0 * v[2] - 1.0
317
+
318
+ return wp.vec3(dx * wy * wz, dy * wz * wx, dz * wx * wy)
319
+
320
+ if self.ORDER == 1 and is_closed(self.family):
321
+ return cache.get_func(element_inner_weight_gradient_linear, self.name)
322
+
323
+ return cache.get_func(element_inner_weight_gradient, self.name)
324
+
325
+ def element_node_hexes(self):
326
+ from warp.fem.utils import grid_to_hexes
327
+
328
+ return grid_to_hexes(self.ORDER, self.ORDER, self.ORDER)
329
+
330
+ def element_node_tets(self):
331
+ from warp.fem.utils import grid_to_tets
332
+
333
+ return grid_to_tets(self.ORDER, self.ORDER, self.ORDER)
334
+
335
+
336
+ class CubeSerendipityShapeFunctions:
337
+ """
338
+ Serendipity element ~ tensor product space without interior nodes
339
+ Edge shape functions are usual Lagrange shape functions times a bilinear function in the normal directions
340
+ Corner shape functions are trilinear shape functions times a function of (x^{d-1} + y^{d-1})
341
+ """
342
+
343
+ # Node categories
344
+ VERTEX = wp.constant(0)
345
+ EDGE_X = wp.constant(1)
346
+ EDGE_Y = wp.constant(2)
347
+
348
+ def __init__(self, degree: int, family: Polynomial):
349
+ if not is_closed(family):
350
+ raise ValueError("A closed polynomial family is required to define serendipity elements")
351
+
352
+ if degree not in [2, 3]:
353
+ raise NotImplementedError("Serendipity element only implemented for order 2 or 3")
354
+
355
+ self.family = family
356
+
357
+ self.ORDER = wp.constant(degree)
358
+ self.NODES_PER_ELEMENT = wp.constant(8 + 12 * (degree - 1))
359
+ self.NODES_PER_EDGE = wp.constant(degree + 1)
360
+
361
+ lobatto_coords, lobatto_weight = quadrature_1d(point_count=degree + 1, family=family)
362
+ lagrange_scale = lagrange_scales(lobatto_coords)
363
+
364
+ NodeVec = wp.types.vector(length=degree + 1, dtype=wp.float32)
365
+ self.LOBATTO_COORDS = wp.constant(NodeVec(lobatto_coords))
366
+ self.LOBATTO_WEIGHT = wp.constant(NodeVec(lobatto_weight))
367
+ self.LAGRANGE_SCALE = wp.constant(NodeVec(lagrange_scale))
368
+ self.ORDER_PLUS_ONE = wp.constant(self.ORDER + 1)
369
+
370
+ self.node_type_and_type_index = self._get_node_type_and_type_index()
371
+ self._node_lobatto_indices = self._get_node_lobatto_indices()
372
+
373
+ @property
374
+ def name(self) -> str:
375
+ return f"Cube_S{self.ORDER}_{self.family}"
376
+
377
+ def _get_node_type_and_type_index(self):
378
+ @cache.dynamic_func(suffix=self.name)
379
+ def node_type_and_index(
380
+ node_index_in_elt: int,
381
+ ):
382
+ if node_index_in_elt < 8:
383
+ return CubeSerendipityShapeFunctions.VERTEX, node_index_in_elt
384
+
385
+ type_index = (node_index_in_elt - 8) // 3
386
+ side = node_index_in_elt - 8 - 3 * type_index
387
+ return CubeSerendipityShapeFunctions.EDGE_X + side, type_index
388
+
389
+ return node_type_and_index
390
+
391
+ @wp.func
392
+ def _vertex_coords(vidx_in_cell: int):
393
+ x = vidx_in_cell // 4
394
+ y = (vidx_in_cell - 4 * x) // 2
395
+ z = vidx_in_cell - 4 * x - 2 * y
396
+ return wp.vec3i(x, y, z)
397
+
398
+ @wp.func
399
+ def _edge_coords(type_index: int):
400
+ index_in_side = type_index // 4
401
+ side_offset = type_index - 4 * index_in_side
402
+ return (wp.vec3i(index_in_side + 1, side_offset // 2, side_offset % 2),)
403
+
404
+ @wp.func
405
+ def _edge_axis(node_type: int):
406
+ return node_type - CubeSerendipityShapeFunctions.EDGE_X
407
+
408
+ @wp.func
409
+ def _cube_edge_index(node_type: int, type_index: int):
410
+ index_in_side = type_index // 4
411
+ side_offset = type_index - 4 * index_in_side
412
+
413
+ return _CUBE_EDGE_INDICES[node_type - CubeSerendipityShapeFunctions.EDGE_X, side_offset], index_in_side
414
+
415
+ def _get_node_lobatto_indices(self):
416
+ ORDER = self.ORDER
417
+
418
+ @cache.dynamic_func(suffix=self.name)
419
+ def node_lobatto_indices(node_type: int, type_index: int):
420
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
421
+ return CubeSerendipityShapeFunctions._vertex_coords(type_index) * ORDER
422
+
423
+ axis = CubeSerendipityShapeFunctions._edge_axis(node_type)
424
+ local_coords = CubeSerendipityShapeFunctions._edge_coords(type_index)
425
+
426
+ local_indices = wp.vec3i(local_coords[0], local_coords[1] * ORDER, local_coords[2] * ORDER)
427
+
428
+ return Grid3D._local_to_world(axis, local_indices)
429
+
430
+ return node_lobatto_indices
431
+
432
+ def make_node_coords_in_element(self):
433
+ LOBATTO_COORDS = self.LOBATTO_COORDS
434
+
435
+ @cache.dynamic_func(suffix=self.name)
436
+ def node_coords_in_element(
437
+ node_index_in_elt: int,
438
+ ):
439
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
440
+ node_coords = self._node_lobatto_indices(node_type, type_index)
441
+ return Coords(
442
+ LOBATTO_COORDS[node_coords[0]], LOBATTO_COORDS[node_coords[1]], LOBATTO_COORDS[node_coords[2]]
443
+ )
444
+
445
+ return node_coords_in_element
446
+
447
+ def make_node_quadrature_weight(self):
448
+ ORDER = self.ORDER
449
+
450
+ @cache.dynamic_func(suffix=self.name)
451
+ def node_quadrature_weight(
452
+ node_index_in_elt: int,
453
+ ):
454
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
455
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
456
+ return 1.0 / float(8 * ORDER * ORDER * ORDER)
457
+
458
+ return (1.0 - 1.0 / float(ORDER * ORDER * ORDER)) / float(12 * (ORDER - 1))
459
+
460
+ return node_quadrature_weight
461
+
462
+ def make_trace_node_quadrature_weight(self):
463
+ ORDER = self.ORDER
464
+
465
+ @cache.dynamic_func(suffix=self.name)
466
+ def trace_node_quadrature_weight(
467
+ node_index_in_elt: int,
468
+ ):
469
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
470
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
471
+ return 0.25 / float(ORDER * ORDER)
472
+
473
+ return (0.25 - 0.25 / float(ORDER * ORDER)) / float(ORDER - 1)
474
+
475
+ return trace_node_quadrature_weight
476
+
477
+ def make_element_inner_weight(self):
478
+ ORDER = self.ORDER
479
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
480
+
481
+ LOBATTO_COORDS = self.LOBATTO_COORDS
482
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
483
+
484
+ DEGREE_3_SPHERE_RAD = wp.constant(2 * 0.5**2 + (0.5 - LOBATTO_COORDS[1]) ** 2)
485
+ DEGREE_3_SPHERE_SCALE = 1.0 / (0.75 - DEGREE_3_SPHERE_RAD)
486
+
487
+ @cache.dynamic_func(suffix=self.name)
488
+ def element_inner_weight(
489
+ coords: Coords,
490
+ node_index_in_elt: int,
491
+ ):
492
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
493
+
494
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
495
+ node_ijk = CubeSerendipityShapeFunctions._vertex_coords(type_index)
496
+
497
+ cx = wp.select(node_ijk[0] == 0, coords[0], 1.0 - coords[0])
498
+ cy = wp.select(node_ijk[1] == 0, coords[1], 1.0 - coords[1])
499
+ cz = wp.select(node_ijk[2] == 0, coords[2], 1.0 - coords[2])
500
+
501
+ w = cx * cy * cz
502
+
503
+ if ORDER == 2:
504
+ w *= cx + cy + cz - 3.0 + LOBATTO_COORDS[1]
505
+ return w * LAGRANGE_SCALE[0]
506
+ if ORDER == 3:
507
+ w *= (
508
+ (cx - 0.5) * (cx - 0.5)
509
+ + (cy - 0.5) * (cy - 0.5)
510
+ + (cz - 0.5) * (cz - 0.5)
511
+ - DEGREE_3_SPHERE_RAD
512
+ )
513
+ return w * DEGREE_3_SPHERE_SCALE
514
+
515
+ axis = CubeSerendipityShapeFunctions._edge_axis(node_type)
516
+
517
+ node_all = CubeSerendipityShapeFunctions._edge_coords(type_index)
518
+
519
+ local_coords = Grid3D._world_to_local(axis, coords)
520
+
521
+ w = float(1.0)
522
+ w *= wp.select(node_all[1] == 0, local_coords[1], 1.0 - local_coords[1])
523
+ w *= wp.select(node_all[2] == 0, local_coords[2], 1.0 - local_coords[2])
524
+
525
+ for k in range(ORDER_PLUS_ONE):
526
+ if k != node_all[0]:
527
+ w *= local_coords[0] - LOBATTO_COORDS[k]
528
+ w *= LAGRANGE_SCALE[node_all[0]]
529
+
530
+ return w
531
+
532
+ return element_inner_weight
533
+
534
+ def make_element_inner_weight_gradient(self):
535
+ ORDER = self.ORDER
536
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
537
+ LOBATTO_COORDS = self.LOBATTO_COORDS
538
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
539
+
540
+ DEGREE_3_SPHERE_RAD = wp.constant(2 * 0.5**2 + (0.5 - LOBATTO_COORDS[1]) ** 2)
541
+ DEGREE_3_SPHERE_SCALE = 1.0 / (0.75 - DEGREE_3_SPHERE_RAD)
542
+
543
+ @cache.dynamic_func(suffix=self.name)
544
+ def element_inner_weight_gradient(
545
+ coords: Coords,
546
+ node_index_in_elt: int,
547
+ ):
548
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
549
+
550
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
551
+ node_ijk = CubeSerendipityShapeFunctions._vertex_coords(type_index)
552
+
553
+ cx = wp.select(node_ijk[0] == 0, coords[0], 1.0 - coords[0])
554
+ cy = wp.select(node_ijk[1] == 0, coords[1], 1.0 - coords[1])
555
+ cz = wp.select(node_ijk[2] == 0, coords[2], 1.0 - coords[2])
556
+
557
+ gx = wp.select(node_ijk[0] == 0, 1.0, -1.0)
558
+ gy = wp.select(node_ijk[1] == 0, 1.0, -1.0)
559
+ gz = wp.select(node_ijk[2] == 0, 1.0, -1.0)
560
+
561
+ if ORDER == 2:
562
+ w = cx + cy + cz - 3.0 + LOBATTO_COORDS[1]
563
+ grad_x = cy * cz * gx * (w + cx)
564
+ grad_y = cz * cx * gy * (w + cy)
565
+ grad_z = cx * cy * gz * (w + cz)
566
+
567
+ return wp.vec3(grad_x, grad_y, grad_z) * LAGRANGE_SCALE[0]
568
+
569
+ if ORDER == 3:
570
+ w = (
571
+ (cx - 0.5) * (cx - 0.5)
572
+ + (cy - 0.5) * (cy - 0.5)
573
+ + (cz - 0.5) * (cz - 0.5)
574
+ - DEGREE_3_SPHERE_RAD
575
+ )
576
+
577
+ dw_dcx = 2.0 * cx - 1.0
578
+ dw_dcy = 2.0 * cy - 1.0
579
+ dw_dcz = 2.0 * cz - 1.0
580
+ grad_x = cy * cz * gx * (w + dw_dcx * cx)
581
+ grad_y = cz * cx * gy * (w + dw_dcy * cy)
582
+ grad_z = cx * cy * gz * (w + dw_dcz * cz)
583
+
584
+ return wp.vec3(grad_x, grad_y, grad_z) * DEGREE_3_SPHERE_SCALE
585
+
586
+ axis = CubeSerendipityShapeFunctions._edge_axis(node_type)
587
+ node_all = CubeSerendipityShapeFunctions._edge_coords(type_index)
588
+
589
+ local_coords = Grid3D._world_to_local(axis, coords)
590
+
591
+ w_long = wp.select(node_all[1] == 0, local_coords[1], 1.0 - local_coords[1])
592
+ w_lat = wp.select(node_all[2] == 0, local_coords[2], 1.0 - local_coords[2])
593
+
594
+ g_long = wp.select(node_all[1] == 0, 1.0, -1.0)
595
+ g_lat = wp.select(node_all[2] == 0, 1.0, -1.0)
596
+
597
+ w_alt = LAGRANGE_SCALE[node_all[0]]
598
+ g_alt = float(0.0)
599
+ prefix_alt = LAGRANGE_SCALE[node_all[0]]
600
+ for k in range(ORDER_PLUS_ONE):
601
+ if k != node_all[0]:
602
+ delta_alt = local_coords[0] - LOBATTO_COORDS[k]
603
+ w_alt *= delta_alt
604
+ g_alt = g_alt * delta_alt + prefix_alt
605
+ prefix_alt *= delta_alt
606
+
607
+ local_grad = wp.vec3(g_alt * w_long * w_lat, w_alt * g_long * w_lat, w_alt * w_long * g_lat)
608
+
609
+ return Grid3D._local_to_world(axis, local_grad)
610
+
611
+ return element_inner_weight_gradient
612
+
613
+ def element_node_tets(self):
614
+ from warp.fem.utils import grid_to_tets
615
+
616
+ if self.ORDER == 2:
617
+ element_tets = np.array(
618
+ [
619
+ [0, 8, 9, 10],
620
+ [1, 11, 10, 15],
621
+ [2, 9, 14, 13],
622
+ [3, 15, 13, 17],
623
+ [4, 12, 8, 16],
624
+ [5, 18, 16, 11],
625
+ [6, 14, 12, 19],
626
+ [7, 19, 18, 17],
627
+ [16, 12, 18, 11],
628
+ [8, 16, 12, 11],
629
+ [12, 19, 18, 14],
630
+ [14, 19, 17, 18],
631
+ [10, 9, 15, 8],
632
+ [10, 8, 11, 15],
633
+ [9, 13, 15, 14],
634
+ [13, 14, 17, 15],
635
+ ]
636
+ )
637
+
638
+ middle_hex = np.array([8, 11, 9, 15, 12, 18, 14, 17])
639
+ middle_tets = middle_hex[grid_to_tets(1, 1, 1)]
640
+
641
+ return np.concatenate((element_tets, middle_tets))
642
+
643
+ raise NotImplementedError()
644
+
645
+
646
+ class CubeNonConformingPolynomialShapeFunctions:
647
+ # embeds the largest regular tet centered at (0.5, 0.5, 0.5) into the reference cube
648
+
649
+ _tet_height = 2.0 / 3.0
650
+ _tet_side = math.sqrt(3.0 / 2.0) * _tet_height
651
+ _tet_face_height = math.sqrt(3.0) / 2.0 * _tet_side
652
+
653
+ _tet_to_cube = np.array(
654
+ [
655
+ [_tet_side, _tet_side / 2.0, _tet_side / 2.0],
656
+ [0.0, _tet_face_height, _tet_face_height / 3.0],
657
+ [0.0, 0.0, _tet_height],
658
+ ]
659
+ )
660
+
661
+ _TET_OFFSET = wp.constant(wp.vec3(0.5 - 0.5 * _tet_side, 0.5 - _tet_face_height / 3.0, 0.5 - 0.25 * _tet_height))
662
+
663
+ def __init__(self, degree: int):
664
+ self._tet_shape = TetrahedronPolynomialShapeFunctions(degree=degree)
665
+ self.ORDER = self._tet_shape.ORDER
666
+ self.NODES_PER_ELEMENT = self._tet_shape.NODES_PER_ELEMENT
667
+
668
+ self.element_node_tets = self._tet_shape.element_node_tets
669
+
670
+ @property
671
+ def name(self) -> str:
672
+ return f"Cube_P{self.ORDER}d"
673
+
674
+ def make_node_coords_in_element(self):
675
+ node_coords_in_tet = self._tet_shape.make_node_coords_in_element()
676
+
677
+ TET_TO_CUBE = wp.constant(wp.mat33(self._tet_to_cube))
678
+
679
+ @cache.dynamic_func(suffix=self.name)
680
+ def node_coords_in_element(
681
+ node_index_in_elt: int,
682
+ ):
683
+ tet_coords = node_coords_in_tet(node_index_in_elt)
684
+ return TET_TO_CUBE * tet_coords + CubeNonConformingPolynomialShapeFunctions._TET_OFFSET
685
+
686
+ return node_coords_in_element
687
+
688
+ def make_node_quadrature_weight(self):
689
+ NODES_PER_ELEMENT = self.NODES_PER_ELEMENT
690
+
691
+ @cache.dynamic_func(suffix=self.name)
692
+ def node_uniform_quadrature_weight(
693
+ node_index_in_elt: int,
694
+ ):
695
+ return 1.0 / float(NODES_PER_ELEMENT)
696
+
697
+ return node_uniform_quadrature_weight
698
+
699
+ def make_trace_node_quadrature_weight(self):
700
+ # Non-conforming, zero measure on sides
701
+
702
+ @wp.func
703
+ def zero(node_index_in_elt: int):
704
+ return 0.0
705
+
706
+ return zero
707
+
708
+ def make_element_inner_weight(self):
709
+ tet_inner_weight = self._tet_shape.make_element_inner_weight()
710
+
711
+ CUBE_TO_TET = wp.constant(wp.mat33(np.linalg.inv(self._tet_to_cube)))
712
+
713
+ @cache.dynamic_func(suffix=self.name)
714
+ def element_inner_weight(
715
+ coords: Coords,
716
+ node_index_in_elt: int,
717
+ ):
718
+ tet_coords = CUBE_TO_TET * (coords - CubeNonConformingPolynomialShapeFunctions._TET_OFFSET)
719
+
720
+ return tet_inner_weight(tet_coords, node_index_in_elt)
721
+
722
+ return element_inner_weight
723
+
724
+ def make_element_inner_weight_gradient(self):
725
+ tet_inner_weight_gradient = self._tet_shape.make_element_inner_weight_gradient()
726
+
727
+ CUBE_TO_TET = wp.constant(wp.mat33(np.linalg.inv(self._tet_to_cube)))
728
+
729
+ @cache.dynamic_func(suffix=self.name)
730
+ def element_inner_weight_gradient(
731
+ coords: Coords,
732
+ node_index_in_elt: int,
733
+ ):
734
+ tet_coords = CUBE_TO_TET * (coords - CubeNonConformingPolynomialShapeFunctions._TET_OFFSET)
735
+ grad = tet_inner_weight_gradient(tet_coords, node_index_in_elt)
736
+ return wp.transpose(CUBE_TO_TET) * grad
737
+
738
+ return element_inner_weight_gradient