warp-lang 1.0.2__py3-none-win_amd64.whl → 1.2.0__py3-none-win_amd64.whl

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

Potentially problematic release.


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

Files changed (356) hide show
  1. warp/__init__.py +108 -97
  2. warp/__init__.pyi +1 -1
  3. warp/bin/warp-clang.dll +0 -0
  4. warp/bin/warp.dll +0 -0
  5. warp/build.py +88 -113
  6. warp/build_dll.py +383 -375
  7. warp/builtins.py +3693 -3354
  8. warp/codegen.py +2925 -2792
  9. warp/config.py +40 -36
  10. warp/constants.py +49 -45
  11. warp/context.py +5409 -5102
  12. warp/dlpack.py +442 -442
  13. warp/examples/__init__.py +16 -16
  14. warp/examples/assets/bear.usd +0 -0
  15. warp/examples/assets/bunny.usd +0 -0
  16. warp/examples/assets/cartpole.urdf +110 -110
  17. warp/examples/assets/crazyflie.usd +0 -0
  18. warp/examples/assets/cube.usd +0 -0
  19. warp/examples/assets/nv_ant.xml +92 -92
  20. warp/examples/assets/nv_humanoid.xml +183 -183
  21. warp/examples/assets/quadruped.urdf +267 -267
  22. warp/examples/assets/rocks.nvdb +0 -0
  23. warp/examples/assets/rocks.usd +0 -0
  24. warp/examples/assets/sphere.usd +0 -0
  25. warp/examples/benchmarks/benchmark_api.py +381 -383
  26. warp/examples/benchmarks/benchmark_cloth.py +278 -277
  27. warp/examples/benchmarks/benchmark_cloth_cupy.py +88 -88
  28. warp/examples/benchmarks/benchmark_cloth_jax.py +97 -100
  29. warp/examples/benchmarks/benchmark_cloth_numba.py +146 -142
  30. warp/examples/benchmarks/benchmark_cloth_numpy.py +77 -77
  31. warp/examples/benchmarks/benchmark_cloth_pytorch.py +86 -86
  32. warp/examples/benchmarks/benchmark_cloth_taichi.py +112 -112
  33. warp/examples/benchmarks/benchmark_cloth_warp.py +145 -146
  34. warp/examples/benchmarks/benchmark_launches.py +293 -295
  35. warp/examples/browse.py +29 -29
  36. warp/examples/core/example_dem.py +232 -219
  37. warp/examples/core/example_fluid.py +291 -267
  38. warp/examples/core/example_graph_capture.py +142 -126
  39. warp/examples/core/example_marching_cubes.py +186 -174
  40. warp/examples/core/example_mesh.py +172 -155
  41. warp/examples/core/example_mesh_intersect.py +203 -193
  42. warp/examples/core/example_nvdb.py +174 -170
  43. warp/examples/core/example_raycast.py +103 -90
  44. warp/examples/core/example_raymarch.py +197 -178
  45. warp/examples/core/example_render_opengl.py +183 -141
  46. warp/examples/core/example_sph.py +403 -387
  47. warp/examples/core/example_torch.py +219 -181
  48. warp/examples/core/example_wave.py +261 -248
  49. warp/examples/fem/bsr_utils.py +378 -380
  50. warp/examples/fem/example_apic_fluid.py +432 -389
  51. warp/examples/fem/example_burgers.py +262 -0
  52. warp/examples/fem/example_convection_diffusion.py +180 -168
  53. warp/examples/fem/example_convection_diffusion_dg.py +217 -209
  54. warp/examples/fem/example_deformed_geometry.py +175 -159
  55. warp/examples/fem/example_diffusion.py +199 -173
  56. warp/examples/fem/example_diffusion_3d.py +178 -152
  57. warp/examples/fem/example_diffusion_mgpu.py +219 -214
  58. warp/examples/fem/example_mixed_elasticity.py +242 -222
  59. warp/examples/fem/example_navier_stokes.py +257 -243
  60. warp/examples/fem/example_stokes.py +218 -192
  61. warp/examples/fem/example_stokes_transfer.py +263 -249
  62. warp/examples/fem/mesh_utils.py +133 -109
  63. warp/examples/fem/plot_utils.py +292 -287
  64. warp/examples/optim/example_bounce.py +258 -246
  65. warp/examples/optim/example_cloth_throw.py +220 -209
  66. warp/examples/optim/example_diffray.py +564 -536
  67. warp/examples/optim/example_drone.py +862 -835
  68. warp/examples/optim/example_inverse_kinematics.py +174 -168
  69. warp/examples/optim/example_inverse_kinematics_torch.py +183 -169
  70. warp/examples/optim/example_spring_cage.py +237 -231
  71. warp/examples/optim/example_trajectory.py +221 -199
  72. warp/examples/optim/example_walker.py +304 -293
  73. warp/examples/sim/example_cartpole.py +137 -129
  74. warp/examples/sim/example_cloth.py +194 -186
  75. warp/examples/sim/example_granular.py +122 -111
  76. warp/examples/sim/example_granular_collision_sdf.py +195 -186
  77. warp/examples/sim/example_jacobian_ik.py +234 -214
  78. warp/examples/sim/example_particle_chain.py +116 -105
  79. warp/examples/sim/example_quadruped.py +191 -180
  80. warp/examples/sim/example_rigid_chain.py +195 -187
  81. warp/examples/sim/example_rigid_contact.py +187 -177
  82. warp/examples/sim/example_rigid_force.py +125 -125
  83. warp/examples/sim/example_rigid_gyroscopic.py +107 -95
  84. warp/examples/sim/example_rigid_soft_contact.py +132 -122
  85. warp/examples/sim/example_soft_body.py +188 -177
  86. warp/fabric.py +337 -335
  87. warp/fem/__init__.py +61 -27
  88. warp/fem/cache.py +403 -388
  89. warp/fem/dirichlet.py +178 -179
  90. warp/fem/domain.py +262 -263
  91. warp/fem/field/__init__.py +100 -101
  92. warp/fem/field/field.py +148 -149
  93. warp/fem/field/nodal_field.py +298 -299
  94. warp/fem/field/restriction.py +22 -21
  95. warp/fem/field/test.py +180 -181
  96. warp/fem/field/trial.py +183 -183
  97. warp/fem/geometry/__init__.py +16 -19
  98. warp/fem/geometry/closest_point.py +69 -70
  99. warp/fem/geometry/deformed_geometry.py +270 -271
  100. warp/fem/geometry/element.py +748 -744
  101. warp/fem/geometry/geometry.py +184 -186
  102. warp/fem/geometry/grid_2d.py +380 -373
  103. warp/fem/geometry/grid_3d.py +437 -435
  104. warp/fem/geometry/hexmesh.py +953 -953
  105. warp/fem/geometry/nanogrid.py +455 -0
  106. warp/fem/geometry/partition.py +374 -376
  107. warp/fem/geometry/quadmesh_2d.py +532 -532
  108. warp/fem/geometry/tetmesh.py +840 -840
  109. warp/fem/geometry/trimesh_2d.py +577 -577
  110. warp/fem/integrate.py +1684 -1615
  111. warp/fem/operator.py +190 -191
  112. warp/fem/polynomial.py +214 -213
  113. warp/fem/quadrature/__init__.py +2 -2
  114. warp/fem/quadrature/pic_quadrature.py +243 -245
  115. warp/fem/quadrature/quadrature.py +295 -294
  116. warp/fem/space/__init__.py +179 -292
  117. warp/fem/space/basis_space.py +522 -489
  118. warp/fem/space/collocated_function_space.py +100 -105
  119. warp/fem/space/dof_mapper.py +236 -236
  120. warp/fem/space/function_space.py +148 -145
  121. warp/fem/space/grid_2d_function_space.py +148 -267
  122. warp/fem/space/grid_3d_function_space.py +167 -306
  123. warp/fem/space/hexmesh_function_space.py +253 -352
  124. warp/fem/space/nanogrid_function_space.py +202 -0
  125. warp/fem/space/partition.py +350 -350
  126. warp/fem/space/quadmesh_2d_function_space.py +261 -369
  127. warp/fem/space/restriction.py +161 -160
  128. warp/fem/space/shape/__init__.py +90 -15
  129. warp/fem/space/shape/cube_shape_function.py +728 -738
  130. warp/fem/space/shape/shape_function.py +102 -103
  131. warp/fem/space/shape/square_shape_function.py +611 -611
  132. warp/fem/space/shape/tet_shape_function.py +565 -567
  133. warp/fem/space/shape/triangle_shape_function.py +429 -429
  134. warp/fem/space/tetmesh_function_space.py +224 -292
  135. warp/fem/space/topology.py +297 -295
  136. warp/fem/space/trimesh_2d_function_space.py +153 -221
  137. warp/fem/types.py +77 -77
  138. warp/fem/utils.py +495 -495
  139. warp/jax.py +166 -141
  140. warp/jax_experimental.py +341 -339
  141. warp/native/array.h +1081 -1025
  142. warp/native/builtin.h +1603 -1560
  143. warp/native/bvh.cpp +402 -398
  144. warp/native/bvh.cu +533 -525
  145. warp/native/bvh.h +430 -429
  146. warp/native/clang/clang.cpp +496 -464
  147. warp/native/crt.cpp +42 -32
  148. warp/native/crt.h +352 -335
  149. warp/native/cuda_crt.h +1049 -1049
  150. warp/native/cuda_util.cpp +549 -540
  151. warp/native/cuda_util.h +288 -203
  152. warp/native/cutlass_gemm.cpp +34 -34
  153. warp/native/cutlass_gemm.cu +372 -372
  154. warp/native/error.cpp +66 -66
  155. warp/native/error.h +27 -27
  156. warp/native/exports.h +187 -0
  157. warp/native/fabric.h +228 -228
  158. warp/native/hashgrid.cpp +301 -278
  159. warp/native/hashgrid.cu +78 -77
  160. warp/native/hashgrid.h +227 -227
  161. warp/native/initializer_array.h +32 -32
  162. warp/native/intersect.h +1204 -1204
  163. warp/native/intersect_adj.h +365 -365
  164. warp/native/intersect_tri.h +322 -322
  165. warp/native/marching.cpp +2 -2
  166. warp/native/marching.cu +497 -497
  167. warp/native/marching.h +2 -2
  168. warp/native/mat.h +1545 -1498
  169. warp/native/matnn.h +333 -333
  170. warp/native/mesh.cpp +203 -203
  171. warp/native/mesh.cu +292 -293
  172. warp/native/mesh.h +1887 -1887
  173. warp/native/nanovdb/GridHandle.h +366 -0
  174. warp/native/nanovdb/HostBuffer.h +590 -0
  175. warp/native/nanovdb/NanoVDB.h +6624 -4782
  176. warp/native/nanovdb/PNanoVDB.h +3390 -2553
  177. warp/native/noise.h +850 -850
  178. warp/native/quat.h +1112 -1085
  179. warp/native/rand.h +303 -299
  180. warp/native/range.h +108 -108
  181. warp/native/reduce.cpp +156 -156
  182. warp/native/reduce.cu +348 -348
  183. warp/native/runlength_encode.cpp +61 -61
  184. warp/native/runlength_encode.cu +46 -46
  185. warp/native/scan.cpp +30 -30
  186. warp/native/scan.cu +36 -36
  187. warp/native/scan.h +7 -7
  188. warp/native/solid_angle.h +442 -442
  189. warp/native/sort.cpp +94 -94
  190. warp/native/sort.cu +97 -97
  191. warp/native/sort.h +14 -14
  192. warp/native/sparse.cpp +337 -337
  193. warp/native/sparse.cu +544 -544
  194. warp/native/spatial.h +630 -630
  195. warp/native/svd.h +562 -562
  196. warp/native/temp_buffer.h +30 -30
  197. warp/native/vec.h +1177 -1133
  198. warp/native/volume.cpp +529 -297
  199. warp/native/volume.cu +58 -32
  200. warp/native/volume.h +960 -538
  201. warp/native/volume_builder.cu +446 -425
  202. warp/native/volume_builder.h +34 -19
  203. warp/native/volume_impl.h +61 -0
  204. warp/native/warp.cpp +1057 -1052
  205. warp/native/warp.cu +2949 -2828
  206. warp/native/warp.h +321 -305
  207. warp/optim/__init__.py +9 -9
  208. warp/optim/adam.py +120 -120
  209. warp/optim/linear.py +1104 -939
  210. warp/optim/sgd.py +104 -92
  211. warp/render/__init__.py +10 -10
  212. warp/render/render_opengl.py +3356 -3204
  213. warp/render/render_usd.py +768 -749
  214. warp/render/utils.py +152 -150
  215. warp/sim/__init__.py +52 -59
  216. warp/sim/articulation.py +685 -685
  217. warp/sim/collide.py +1594 -1590
  218. warp/sim/import_mjcf.py +489 -481
  219. warp/sim/import_snu.py +220 -221
  220. warp/sim/import_urdf.py +536 -516
  221. warp/sim/import_usd.py +887 -881
  222. warp/sim/inertia.py +316 -317
  223. warp/sim/integrator.py +234 -233
  224. warp/sim/integrator_euler.py +1956 -1956
  225. warp/sim/integrator_featherstone.py +1917 -1991
  226. warp/sim/integrator_xpbd.py +3288 -3312
  227. warp/sim/model.py +4473 -4314
  228. warp/sim/particles.py +113 -112
  229. warp/sim/render.py +417 -403
  230. warp/sim/utils.py +413 -410
  231. warp/sparse.py +1289 -1227
  232. warp/stubs.py +2192 -2469
  233. warp/tape.py +1162 -225
  234. warp/tests/__init__.py +1 -1
  235. warp/tests/__main__.py +4 -4
  236. warp/tests/assets/test_index_grid.nvdb +0 -0
  237. warp/tests/assets/torus.usda +105 -105
  238. warp/tests/aux_test_class_kernel.py +26 -26
  239. warp/tests/aux_test_compile_consts_dummy.py +10 -10
  240. warp/tests/aux_test_conditional_unequal_types_kernels.py +21 -21
  241. warp/tests/aux_test_dependent.py +20 -22
  242. warp/tests/aux_test_grad_customs.py +21 -23
  243. warp/tests/aux_test_reference.py +9 -11
  244. warp/tests/aux_test_reference_reference.py +8 -10
  245. warp/tests/aux_test_square.py +15 -17
  246. warp/tests/aux_test_unresolved_func.py +14 -14
  247. warp/tests/aux_test_unresolved_symbol.py +14 -14
  248. warp/tests/disabled_kinematics.py +237 -239
  249. warp/tests/run_coverage_serial.py +31 -31
  250. warp/tests/test_adam.py +155 -157
  251. warp/tests/test_arithmetic.py +1088 -1124
  252. warp/tests/test_array.py +2415 -2326
  253. warp/tests/test_array_reduce.py +148 -150
  254. warp/tests/test_async.py +666 -656
  255. warp/tests/test_atomic.py +139 -141
  256. warp/tests/test_bool.py +212 -149
  257. warp/tests/test_builtins_resolution.py +1290 -1292
  258. warp/tests/test_bvh.py +162 -171
  259. warp/tests/test_closest_point_edge_edge.py +227 -228
  260. warp/tests/test_codegen.py +562 -553
  261. warp/tests/test_compile_consts.py +217 -101
  262. warp/tests/test_conditional.py +244 -246
  263. warp/tests/test_copy.py +230 -215
  264. warp/tests/test_ctypes.py +630 -632
  265. warp/tests/test_dense.py +65 -67
  266. warp/tests/test_devices.py +89 -98
  267. warp/tests/test_dlpack.py +528 -529
  268. warp/tests/test_examples.py +403 -378
  269. warp/tests/test_fabricarray.py +952 -955
  270. warp/tests/test_fast_math.py +60 -54
  271. warp/tests/test_fem.py +1298 -1278
  272. warp/tests/test_fp16.py +128 -130
  273. warp/tests/test_func.py +336 -337
  274. warp/tests/test_generics.py +596 -571
  275. warp/tests/test_grad.py +885 -640
  276. warp/tests/test_grad_customs.py +331 -336
  277. warp/tests/test_hash_grid.py +208 -164
  278. warp/tests/test_import.py +37 -39
  279. warp/tests/test_indexedarray.py +1132 -1134
  280. warp/tests/test_intersect.py +65 -67
  281. warp/tests/test_jax.py +305 -307
  282. warp/tests/test_large.py +169 -164
  283. warp/tests/test_launch.py +352 -354
  284. warp/tests/test_lerp.py +217 -261
  285. warp/tests/test_linear_solvers.py +189 -171
  286. warp/tests/test_lvalue.py +419 -493
  287. warp/tests/test_marching_cubes.py +63 -65
  288. warp/tests/test_mat.py +1799 -1827
  289. warp/tests/test_mat_lite.py +113 -115
  290. warp/tests/test_mat_scalar_ops.py +2905 -2889
  291. warp/tests/test_math.py +124 -193
  292. warp/tests/test_matmul.py +498 -499
  293. warp/tests/test_matmul_lite.py +408 -410
  294. warp/tests/test_mempool.py +186 -190
  295. warp/tests/test_mesh.py +281 -324
  296. warp/tests/test_mesh_query_aabb.py +226 -241
  297. warp/tests/test_mesh_query_point.py +690 -702
  298. warp/tests/test_mesh_query_ray.py +290 -303
  299. warp/tests/test_mlp.py +274 -276
  300. warp/tests/test_model.py +108 -110
  301. warp/tests/test_module_hashing.py +111 -0
  302. warp/tests/test_modules_lite.py +36 -39
  303. warp/tests/test_multigpu.py +161 -163
  304. warp/tests/test_noise.py +244 -248
  305. warp/tests/test_operators.py +248 -250
  306. warp/tests/test_options.py +121 -125
  307. warp/tests/test_peer.py +131 -137
  308. warp/tests/test_pinned.py +76 -78
  309. warp/tests/test_print.py +52 -54
  310. warp/tests/test_quat.py +2084 -2086
  311. warp/tests/test_rand.py +324 -288
  312. warp/tests/test_reload.py +207 -217
  313. warp/tests/test_rounding.py +177 -179
  314. warp/tests/test_runlength_encode.py +188 -190
  315. warp/tests/test_sim_grad.py +241 -0
  316. warp/tests/test_sim_kinematics.py +89 -97
  317. warp/tests/test_smoothstep.py +166 -168
  318. warp/tests/test_snippet.py +303 -266
  319. warp/tests/test_sparse.py +466 -460
  320. warp/tests/test_spatial.py +2146 -2148
  321. warp/tests/test_special_values.py +362 -0
  322. warp/tests/test_streams.py +484 -473
  323. warp/tests/test_struct.py +708 -675
  324. warp/tests/test_tape.py +171 -148
  325. warp/tests/test_torch.py +741 -743
  326. warp/tests/test_transient_module.py +85 -87
  327. warp/tests/test_types.py +554 -659
  328. warp/tests/test_utils.py +488 -499
  329. warp/tests/test_vec.py +1262 -1268
  330. warp/tests/test_vec_lite.py +71 -73
  331. warp/tests/test_vec_scalar_ops.py +2097 -2099
  332. warp/tests/test_verify_fp.py +92 -94
  333. warp/tests/test_volume.py +961 -736
  334. warp/tests/test_volume_write.py +338 -265
  335. warp/tests/unittest_serial.py +38 -37
  336. warp/tests/unittest_suites.py +367 -359
  337. warp/tests/unittest_utils.py +434 -578
  338. warp/tests/unused_test_misc.py +69 -71
  339. warp/tests/walkthrough_debug.py +85 -85
  340. warp/thirdparty/appdirs.py +598 -598
  341. warp/thirdparty/dlpack.py +143 -143
  342. warp/thirdparty/unittest_parallel.py +563 -561
  343. warp/torch.py +321 -295
  344. warp/types.py +4941 -4450
  345. warp/utils.py +1008 -821
  346. {warp_lang-1.0.2.dist-info → warp_lang-1.2.0.dist-info}/LICENSE.md +126 -126
  347. {warp_lang-1.0.2.dist-info → warp_lang-1.2.0.dist-info}/METADATA +365 -400
  348. warp_lang-1.2.0.dist-info/RECORD +359 -0
  349. warp/examples/assets/cube.usda +0 -42
  350. warp/examples/assets/sphere.usda +0 -56
  351. warp/examples/assets/torus.usda +0 -105
  352. warp/examples/fem/example_convection_diffusion_dg0.py +0 -194
  353. warp/native/nanovdb/PNanoVDBWrite.h +0 -295
  354. warp_lang-1.0.2.dist-info/RECORD +0 -352
  355. {warp_lang-1.0.2.dist-info → warp_lang-1.2.0.dist-info}/WHEEL +0 -0
  356. {warp_lang-1.0.2.dist-info → warp_lang-1.2.0.dist-info}/top_level.txt +0 -0
@@ -1,738 +1,728 @@
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
1
+ import math
2
+
3
+ import numpy as np
4
+
5
+ import warp as wp
6
+ from warp.fem import cache
7
+ from warp.fem.geometry import Grid3D
8
+ from warp.fem.polynomial import Polynomial, is_closed, lagrange_scales, quadrature_1d
9
+ from warp.fem.types import Coords
10
+
11
+ from .tet_shape_function import TetrahedronPolynomialShapeFunctions
12
+
13
+
14
+ class CubeTripolynomialShapeFunctions:
15
+ VERTEX = 0
16
+ EDGE = 1
17
+ FACE = 2
18
+ INTERIOR = 3
19
+
20
+ def __init__(self, degree: int, family: Polynomial):
21
+ self.family = family
22
+
23
+ self.ORDER = wp.constant(degree)
24
+ self.NODES_PER_ELEMENT = wp.constant((degree + 1) ** 3)
25
+ self.NODES_PER_EDGE = wp.constant(degree + 1)
26
+
27
+ lobatto_coords, lobatto_weight = quadrature_1d(point_count=degree + 1, family=family)
28
+ lagrange_scale = lagrange_scales(lobatto_coords)
29
+
30
+ NodeVec = wp.types.vector(length=degree + 1, dtype=wp.float32)
31
+ self.LOBATTO_COORDS = wp.constant(NodeVec(lobatto_coords))
32
+ self.LOBATTO_WEIGHT = wp.constant(NodeVec(lobatto_weight))
33
+ self.LAGRANGE_SCALE = wp.constant(NodeVec(lagrange_scale))
34
+ self.ORDER_PLUS_ONE = wp.constant(self.ORDER + 1)
35
+
36
+ self._node_ijk = self._make_node_ijk()
37
+ self.node_type_and_type_index = self._make_node_type_and_type_index()
38
+
39
+ @property
40
+ def name(self) -> str:
41
+ return f"Cube_Q{self.ORDER}_{self.family}"
42
+
43
+ @wp.func
44
+ def _vertex_coords_f(vidx_in_cell: int):
45
+ x = vidx_in_cell // 4
46
+ y = (vidx_in_cell - 4 * x) // 2
47
+ z = vidx_in_cell - 4 * x - 2 * y
48
+ return wp.vec3(float(x), float(y), float(z))
49
+
50
+ def _make_node_ijk(self):
51
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
52
+
53
+ def node_ijk(
54
+ node_index_in_elt: int,
55
+ ):
56
+ node_i = node_index_in_elt // (ORDER_PLUS_ONE * ORDER_PLUS_ONE)
57
+ node_jk = node_index_in_elt - ORDER_PLUS_ONE * ORDER_PLUS_ONE * node_i
58
+ node_j = node_jk // ORDER_PLUS_ONE
59
+ node_k = node_jk - ORDER_PLUS_ONE * node_j
60
+ return node_i, node_j, node_k
61
+
62
+ return cache.get_func(node_ijk, self.name)
63
+
64
+ def _make_node_type_and_type_index(self):
65
+ ORDER = self.ORDER
66
+
67
+ @cache.dynamic_func(suffix=self.name)
68
+ def node_type_and_type_index(
69
+ node_index_in_elt: int,
70
+ ):
71
+ i, j, k = self._node_ijk(node_index_in_elt)
72
+
73
+ zi = wp.select(i == 0, 0, 1)
74
+ zj = wp.select(j == 0, 0, 1)
75
+ zk = wp.select(k == 0, 0, 1)
76
+
77
+ mi = wp.select(i == ORDER, 0, 1)
78
+ mj = wp.select(j == ORDER, 0, 1)
79
+ mk = wp.select(k == ORDER, 0, 1)
80
+
81
+ if zi + mi == 1:
82
+ if zj + mj == 1:
83
+ if zk + mk == 1:
84
+ # vertex
85
+ type_instance = mi * 4 + mj * 2 + mk
86
+ return CubeTripolynomialShapeFunctions.VERTEX, type_instance, 0
87
+
88
+ # z edge
89
+ type_instance = 8 + mi * 2 + mj
90
+ type_index = k - 1
91
+ return CubeTripolynomialShapeFunctions.EDGE, type_instance, type_index
92
+
93
+ if zk + mk == 1:
94
+ # y edge
95
+ type_instance = 4 + mk * 2 + mi
96
+ type_index = j - 1
97
+ return CubeTripolynomialShapeFunctions.EDGE, type_instance, type_index
98
+
99
+ # x face
100
+ type_instance = mi
101
+ type_index = wp.select(mi == 1, (j - 1) * (ORDER - 1) + k - 1, (k - 1) * (ORDER - 1) + j - 1)
102
+ return CubeTripolynomialShapeFunctions.FACE, type_instance, type_index
103
+
104
+ if zj + mj == 1:
105
+ if zk + mk == 1:
106
+ # x edge
107
+ type_instance = mj * 2 + mk
108
+ type_index = i - 1
109
+ return CubeTripolynomialShapeFunctions.EDGE, type_instance, type_index
110
+
111
+ # y face
112
+ type_instance = 2 + mj
113
+ type_index = wp.select(mj == 1, (i - 1) * (ORDER - 1) + k - 1, (k - 1) * (ORDER - 1) + i - 1)
114
+ return CubeTripolynomialShapeFunctions.FACE, type_instance, type_index
115
+
116
+ if zk + mk == 1:
117
+ # z face
118
+ type_instance = 4 + mk
119
+ type_index = wp.select(mk == 1, (j - 1) * (ORDER - 1) + i - 1, (i - 1) * (ORDER - 1) + j - 1)
120
+ return CubeTripolynomialShapeFunctions.FACE, type_instance, type_index
121
+
122
+ type_index = ((i - 1) * (ORDER - 1) + (j - 1)) * (ORDER - 1) + k - 1
123
+ return CubeTripolynomialShapeFunctions.INTERIOR, 0, type_index
124
+
125
+ return node_type_and_type_index
126
+
127
+ def make_node_coords_in_element(self):
128
+ LOBATTO_COORDS = self.LOBATTO_COORDS
129
+
130
+ @cache.dynamic_func(suffix=self.name)
131
+ def node_coords_in_element(
132
+ node_index_in_elt: int,
133
+ ):
134
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
135
+ return Coords(LOBATTO_COORDS[node_i], LOBATTO_COORDS[node_j], LOBATTO_COORDS[node_k])
136
+
137
+ return node_coords_in_element
138
+
139
+ def make_node_quadrature_weight(self):
140
+ ORDER = self.ORDER
141
+ LOBATTO_WEIGHT = self.LOBATTO_WEIGHT
142
+
143
+ def node_quadrature_weight(
144
+ node_index_in_elt: int,
145
+ ):
146
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
147
+ return LOBATTO_WEIGHT[node_i] * LOBATTO_WEIGHT[node_j] * LOBATTO_WEIGHT[node_k]
148
+
149
+ def node_quadrature_weight_linear(
150
+ node_index_in_elt: int,
151
+ ):
152
+ return 0.125
153
+
154
+ if ORDER == 1:
155
+ return cache.get_func(node_quadrature_weight_linear, self.name)
156
+
157
+ return cache.get_func(node_quadrature_weight, self.name)
158
+
159
+ def make_trace_node_quadrature_weight(self):
160
+ ORDER = self.ORDER
161
+ LOBATTO_WEIGHT = self.LOBATTO_WEIGHT
162
+
163
+ def trace_node_quadrature_weight(
164
+ node_index_in_elt: int,
165
+ ):
166
+ # We're either on a side interior or at a vertex
167
+ # If we find one index at extremum, pick the two other
168
+
169
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
170
+
171
+ if node_i == 0 or node_i == ORDER:
172
+ return LOBATTO_WEIGHT[node_j] * LOBATTO_WEIGHT[node_k]
173
+
174
+ if node_j == 0 or node_j == ORDER:
175
+ return LOBATTO_WEIGHT[node_i] * LOBATTO_WEIGHT[node_k]
176
+
177
+ return LOBATTO_WEIGHT[node_i] * LOBATTO_WEIGHT[node_j]
178
+
179
+ def trace_node_quadrature_weight_linear(
180
+ node_index_in_elt: int,
181
+ ):
182
+ return 0.25
183
+
184
+ def trace_node_quadrature_weight_open(
185
+ node_index_in_elt: int,
186
+ ):
187
+ return 0.0
188
+
189
+ if not is_closed(self.family):
190
+ return cache.get_func(trace_node_quadrature_weight_open, self.name)
191
+
192
+ if ORDER == 1:
193
+ return cache.get_func(trace_node_quadrature_weight_linear, self.name)
194
+
195
+ return cache.get_func(trace_node_quadrature_weight, self.name)
196
+
197
+ def make_element_inner_weight(self):
198
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
199
+ LOBATTO_COORDS = self.LOBATTO_COORDS
200
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
201
+
202
+ def element_inner_weight(
203
+ coords: Coords,
204
+ node_index_in_elt: int,
205
+ ):
206
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
207
+
208
+ w = float(1.0)
209
+ for k in range(ORDER_PLUS_ONE):
210
+ if k != node_i:
211
+ w *= coords[0] - LOBATTO_COORDS[k]
212
+ if k != node_j:
213
+ w *= coords[1] - LOBATTO_COORDS[k]
214
+ if k != node_k:
215
+ w *= coords[2] - LOBATTO_COORDS[k]
216
+
217
+ w *= LAGRANGE_SCALE[node_i] * LAGRANGE_SCALE[node_j] * LAGRANGE_SCALE[node_k]
218
+
219
+ return w
220
+
221
+ def element_inner_weight_linear(
222
+ coords: Coords,
223
+ node_index_in_elt: int,
224
+ ):
225
+ v = CubeTripolynomialShapeFunctions._vertex_coords_f(node_index_in_elt)
226
+
227
+ wx = (1.0 - coords[0]) * (1.0 - v[0]) + v[0] * coords[0]
228
+ wy = (1.0 - coords[1]) * (1.0 - v[1]) + v[1] * coords[1]
229
+ wz = (1.0 - coords[2]) * (1.0 - v[2]) + v[2] * coords[2]
230
+ return wx * wy * wz
231
+
232
+ if self.ORDER == 1 and is_closed(self.family):
233
+ return cache.get_func(element_inner_weight_linear, self.name)
234
+
235
+ return cache.get_func(element_inner_weight, self.name)
236
+
237
+ def make_element_inner_weight_gradient(self):
238
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
239
+ LOBATTO_COORDS = self.LOBATTO_COORDS
240
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
241
+
242
+ def element_inner_weight_gradient(
243
+ coords: Coords,
244
+ node_index_in_elt: int,
245
+ ):
246
+ node_i, node_j, node_k = self._node_ijk(node_index_in_elt)
247
+
248
+ prefix_xy = float(1.0)
249
+ prefix_yz = float(1.0)
250
+ prefix_zx = float(1.0)
251
+ for k in range(ORDER_PLUS_ONE):
252
+ if k != node_i:
253
+ prefix_yz *= coords[0] - LOBATTO_COORDS[k]
254
+ if k != node_j:
255
+ prefix_zx *= coords[1] - LOBATTO_COORDS[k]
256
+ if k != node_k:
257
+ prefix_xy *= coords[2] - LOBATTO_COORDS[k]
258
+
259
+ prefix_x = prefix_zx * prefix_xy
260
+ prefix_y = prefix_yz * prefix_xy
261
+ prefix_z = prefix_zx * prefix_yz
262
+
263
+ grad_x = float(0.0)
264
+ grad_y = float(0.0)
265
+ grad_z = float(0.0)
266
+
267
+ for k in range(ORDER_PLUS_ONE):
268
+ if k != node_i:
269
+ delta_x = coords[0] - LOBATTO_COORDS[k]
270
+ grad_x = grad_x * delta_x + prefix_x
271
+ prefix_x *= delta_x
272
+ if k != node_j:
273
+ delta_y = coords[1] - LOBATTO_COORDS[k]
274
+ grad_y = grad_y * delta_y + prefix_y
275
+ prefix_y *= delta_y
276
+ if k != node_k:
277
+ delta_z = coords[2] - LOBATTO_COORDS[k]
278
+ grad_z = grad_z * delta_z + prefix_z
279
+ prefix_z *= delta_z
280
+
281
+ grad = (
282
+ LAGRANGE_SCALE[node_i]
283
+ * LAGRANGE_SCALE[node_j]
284
+ * LAGRANGE_SCALE[node_k]
285
+ * wp.vec3(
286
+ grad_x,
287
+ grad_y,
288
+ grad_z,
289
+ )
290
+ )
291
+
292
+ return grad
293
+
294
+ def element_inner_weight_gradient_linear(
295
+ coords: Coords,
296
+ node_index_in_elt: int,
297
+ ):
298
+ v = CubeTripolynomialShapeFunctions._vertex_coords_f(node_index_in_elt)
299
+
300
+ wx = (1.0 - coords[0]) * (1.0 - v[0]) + v[0] * coords[0]
301
+ wy = (1.0 - coords[1]) * (1.0 - v[1]) + v[1] * coords[1]
302
+ wz = (1.0 - coords[2]) * (1.0 - v[2]) + v[2] * coords[2]
303
+
304
+ dx = 2.0 * v[0] - 1.0
305
+ dy = 2.0 * v[1] - 1.0
306
+ dz = 2.0 * v[2] - 1.0
307
+
308
+ return wp.vec3(dx * wy * wz, dy * wz * wx, dz * wx * wy)
309
+
310
+ if self.ORDER == 1 and is_closed(self.family):
311
+ return cache.get_func(element_inner_weight_gradient_linear, self.name)
312
+
313
+ return cache.get_func(element_inner_weight_gradient, self.name)
314
+
315
+ def element_node_hexes(self):
316
+ from warp.fem.utils import grid_to_hexes
317
+
318
+ return grid_to_hexes(self.ORDER, self.ORDER, self.ORDER)
319
+
320
+ def element_node_tets(self):
321
+ from warp.fem.utils import grid_to_tets
322
+
323
+ return grid_to_tets(self.ORDER, self.ORDER, self.ORDER)
324
+
325
+
326
+ class CubeSerendipityShapeFunctions:
327
+ """
328
+ Serendipity element ~ tensor product space without interior nodes
329
+ Edge shape functions are usual Lagrange shape functions times a bilinear function in the normal directions
330
+ Corner shape functions are trilinear shape functions times a function of (x^{d-1} + y^{d-1})
331
+ """
332
+
333
+ # Node categories
334
+ VERTEX = wp.constant(0)
335
+ EDGE_X = wp.constant(1)
336
+ EDGE_Y = wp.constant(2)
337
+
338
+ def __init__(self, degree: int, family: Polynomial):
339
+ if not is_closed(family):
340
+ raise ValueError("A closed polynomial family is required to define serendipity elements")
341
+
342
+ if degree not in [2, 3]:
343
+ raise NotImplementedError("Serendipity element only implemented for order 2 or 3")
344
+
345
+ self.family = family
346
+
347
+ self.ORDER = wp.constant(degree)
348
+ self.NODES_PER_ELEMENT = wp.constant(8 + 12 * (degree - 1))
349
+ self.NODES_PER_EDGE = wp.constant(degree + 1)
350
+
351
+ lobatto_coords, lobatto_weight = quadrature_1d(point_count=degree + 1, family=family)
352
+ lagrange_scale = lagrange_scales(lobatto_coords)
353
+
354
+ NodeVec = wp.types.vector(length=degree + 1, dtype=wp.float32)
355
+ self.LOBATTO_COORDS = wp.constant(NodeVec(lobatto_coords))
356
+ self.LOBATTO_WEIGHT = wp.constant(NodeVec(lobatto_weight))
357
+ self.LAGRANGE_SCALE = wp.constant(NodeVec(lagrange_scale))
358
+ self.ORDER_PLUS_ONE = wp.constant(self.ORDER + 1)
359
+
360
+ self.node_type_and_type_index = self._get_node_type_and_type_index()
361
+ self._node_lobatto_indices = self._get_node_lobatto_indices()
362
+
363
+ @property
364
+ def name(self) -> str:
365
+ return f"Cube_S{self.ORDER}_{self.family}"
366
+
367
+ def _get_node_type_and_type_index(self):
368
+ @cache.dynamic_func(suffix=self.name)
369
+ def node_type_and_index(
370
+ node_index_in_elt: int,
371
+ ):
372
+ if node_index_in_elt < 8:
373
+ return CubeSerendipityShapeFunctions.VERTEX, node_index_in_elt
374
+
375
+ type_index = (node_index_in_elt - 8) // 3
376
+ side = node_index_in_elt - 8 - 3 * type_index
377
+ return CubeSerendipityShapeFunctions.EDGE_X + side, type_index
378
+
379
+ return node_type_and_index
380
+
381
+ @wp.func
382
+ def _vertex_coords(vidx_in_cell: int):
383
+ x = vidx_in_cell // 4
384
+ y = (vidx_in_cell - 4 * x) // 2
385
+ z = vidx_in_cell - 4 * x - 2 * y
386
+ return wp.vec3i(x, y, z)
387
+
388
+ @wp.func
389
+ def _edge_coords(type_index: int):
390
+ index_in_side = type_index // 4
391
+ side_offset = type_index - 4 * index_in_side
392
+ return wp.vec3i(index_in_side + 1, side_offset // 2, side_offset & 1)
393
+
394
+ @wp.func
395
+ def _edge_axis(node_type: int):
396
+ return node_type - CubeSerendipityShapeFunctions.EDGE_X
397
+
398
+ @wp.func
399
+ def _cube_edge_index(node_type: int, type_index: int):
400
+ index_in_side = type_index // 4
401
+ side_offset = type_index - 4 * index_in_side
402
+
403
+ return 4 * (node_type - CubeSerendipityShapeFunctions.EDGE_X) + side_offset, index_in_side
404
+
405
+ def _get_node_lobatto_indices(self):
406
+ ORDER = self.ORDER
407
+
408
+ @cache.dynamic_func(suffix=self.name)
409
+ def node_lobatto_indices(node_type: int, type_index: int):
410
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
411
+ return CubeSerendipityShapeFunctions._vertex_coords(type_index) * ORDER
412
+
413
+ axis = CubeSerendipityShapeFunctions._edge_axis(node_type)
414
+ local_coords = CubeSerendipityShapeFunctions._edge_coords(type_index)
415
+
416
+ local_indices = wp.vec3i(local_coords[0], local_coords[1] * ORDER, local_coords[2] * ORDER)
417
+
418
+ return Grid3D._local_to_world(axis, local_indices)
419
+
420
+ return node_lobatto_indices
421
+
422
+ def make_node_coords_in_element(self):
423
+ LOBATTO_COORDS = self.LOBATTO_COORDS
424
+
425
+ @cache.dynamic_func(suffix=self.name)
426
+ def node_coords_in_element(
427
+ node_index_in_elt: int,
428
+ ):
429
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
430
+ node_coords = self._node_lobatto_indices(node_type, type_index)
431
+ return Coords(
432
+ LOBATTO_COORDS[node_coords[0]], LOBATTO_COORDS[node_coords[1]], LOBATTO_COORDS[node_coords[2]]
433
+ )
434
+
435
+ return node_coords_in_element
436
+
437
+ def make_node_quadrature_weight(self):
438
+ ORDER = self.ORDER
439
+
440
+ @cache.dynamic_func(suffix=self.name)
441
+ def node_quadrature_weight(
442
+ node_index_in_elt: int,
443
+ ):
444
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
445
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
446
+ return 1.0 / float(8 * ORDER * ORDER * ORDER)
447
+
448
+ return (1.0 - 1.0 / float(ORDER * ORDER * ORDER)) / float(12 * (ORDER - 1))
449
+
450
+ return node_quadrature_weight
451
+
452
+ def make_trace_node_quadrature_weight(self):
453
+ ORDER = self.ORDER
454
+
455
+ @cache.dynamic_func(suffix=self.name)
456
+ def trace_node_quadrature_weight(
457
+ node_index_in_elt: int,
458
+ ):
459
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
460
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
461
+ return 0.25 / float(ORDER * ORDER)
462
+
463
+ return (0.25 - 0.25 / float(ORDER * ORDER)) / float(ORDER - 1)
464
+
465
+ return trace_node_quadrature_weight
466
+
467
+ def make_element_inner_weight(self):
468
+ ORDER = self.ORDER
469
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
470
+
471
+ LOBATTO_COORDS = self.LOBATTO_COORDS
472
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
473
+
474
+ DEGREE_3_SPHERE_RAD = wp.constant(2 * 0.5**2 + (0.5 - LOBATTO_COORDS[1]) ** 2)
475
+ DEGREE_3_SPHERE_SCALE = 1.0 / (0.75 - DEGREE_3_SPHERE_RAD)
476
+
477
+ @cache.dynamic_func(suffix=self.name)
478
+ def element_inner_weight(
479
+ coords: Coords,
480
+ node_index_in_elt: int,
481
+ ):
482
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
483
+
484
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
485
+ node_ijk = CubeSerendipityShapeFunctions._vertex_coords(type_index)
486
+
487
+ cx = wp.select(node_ijk[0] == 0, coords[0], 1.0 - coords[0])
488
+ cy = wp.select(node_ijk[1] == 0, coords[1], 1.0 - coords[1])
489
+ cz = wp.select(node_ijk[2] == 0, coords[2], 1.0 - coords[2])
490
+
491
+ w = cx * cy * cz
492
+
493
+ if ORDER == 2:
494
+ w *= cx + cy + cz - 3.0 + LOBATTO_COORDS[1]
495
+ return w * LAGRANGE_SCALE[0]
496
+ if ORDER == 3:
497
+ w *= (
498
+ (cx - 0.5) * (cx - 0.5)
499
+ + (cy - 0.5) * (cy - 0.5)
500
+ + (cz - 0.5) * (cz - 0.5)
501
+ - DEGREE_3_SPHERE_RAD
502
+ )
503
+ return w * DEGREE_3_SPHERE_SCALE
504
+
505
+ axis = CubeSerendipityShapeFunctions._edge_axis(node_type)
506
+
507
+ node_all = CubeSerendipityShapeFunctions._edge_coords(type_index)
508
+
509
+ local_coords = Grid3D._world_to_local(axis, coords)
510
+
511
+ w = float(1.0)
512
+ w *= wp.select(node_all[1] == 0, local_coords[1], 1.0 - local_coords[1])
513
+ w *= wp.select(node_all[2] == 0, local_coords[2], 1.0 - local_coords[2])
514
+
515
+ for k in range(ORDER_PLUS_ONE):
516
+ if k != node_all[0]:
517
+ w *= local_coords[0] - LOBATTO_COORDS[k]
518
+ w *= LAGRANGE_SCALE[node_all[0]]
519
+
520
+ return w
521
+
522
+ return element_inner_weight
523
+
524
+ def make_element_inner_weight_gradient(self):
525
+ ORDER = self.ORDER
526
+ ORDER_PLUS_ONE = self.ORDER_PLUS_ONE
527
+ LOBATTO_COORDS = self.LOBATTO_COORDS
528
+ LAGRANGE_SCALE = self.LAGRANGE_SCALE
529
+
530
+ DEGREE_3_SPHERE_RAD = wp.constant(2 * 0.5**2 + (0.5 - LOBATTO_COORDS[1]) ** 2)
531
+ DEGREE_3_SPHERE_SCALE = 1.0 / (0.75 - DEGREE_3_SPHERE_RAD)
532
+
533
+ @cache.dynamic_func(suffix=self.name)
534
+ def element_inner_weight_gradient(
535
+ coords: Coords,
536
+ node_index_in_elt: int,
537
+ ):
538
+ node_type, type_index = self.node_type_and_type_index(node_index_in_elt)
539
+
540
+ if node_type == CubeSerendipityShapeFunctions.VERTEX:
541
+ node_ijk = CubeSerendipityShapeFunctions._vertex_coords(type_index)
542
+
543
+ cx = wp.select(node_ijk[0] == 0, coords[0], 1.0 - coords[0])
544
+ cy = wp.select(node_ijk[1] == 0, coords[1], 1.0 - coords[1])
545
+ cz = wp.select(node_ijk[2] == 0, coords[2], 1.0 - coords[2])
546
+
547
+ gx = wp.select(node_ijk[0] == 0, 1.0, -1.0)
548
+ gy = wp.select(node_ijk[1] == 0, 1.0, -1.0)
549
+ gz = wp.select(node_ijk[2] == 0, 1.0, -1.0)
550
+
551
+ if ORDER == 2:
552
+ w = cx + cy + cz - 3.0 + LOBATTO_COORDS[1]
553
+ grad_x = cy * cz * gx * (w + cx)
554
+ grad_y = cz * cx * gy * (w + cy)
555
+ grad_z = cx * cy * gz * (w + cz)
556
+
557
+ return wp.vec3(grad_x, grad_y, grad_z) * LAGRANGE_SCALE[0]
558
+
559
+ if ORDER == 3:
560
+ w = (
561
+ (cx - 0.5) * (cx - 0.5)
562
+ + (cy - 0.5) * (cy - 0.5)
563
+ + (cz - 0.5) * (cz - 0.5)
564
+ - DEGREE_3_SPHERE_RAD
565
+ )
566
+
567
+ dw_dcx = 2.0 * cx - 1.0
568
+ dw_dcy = 2.0 * cy - 1.0
569
+ dw_dcz = 2.0 * cz - 1.0
570
+ grad_x = cy * cz * gx * (w + dw_dcx * cx)
571
+ grad_y = cz * cx * gy * (w + dw_dcy * cy)
572
+ grad_z = cx * cy * gz * (w + dw_dcz * cz)
573
+
574
+ return wp.vec3(grad_x, grad_y, grad_z) * DEGREE_3_SPHERE_SCALE
575
+
576
+ axis = CubeSerendipityShapeFunctions._edge_axis(node_type)
577
+ node_all = CubeSerendipityShapeFunctions._edge_coords(type_index)
578
+
579
+ local_coords = Grid3D._world_to_local(axis, coords)
580
+
581
+ w_long = wp.select(node_all[1] == 0, local_coords[1], 1.0 - local_coords[1])
582
+ w_lat = wp.select(node_all[2] == 0, local_coords[2], 1.0 - local_coords[2])
583
+
584
+ g_long = wp.select(node_all[1] == 0, 1.0, -1.0)
585
+ g_lat = wp.select(node_all[2] == 0, 1.0, -1.0)
586
+
587
+ w_alt = LAGRANGE_SCALE[node_all[0]]
588
+ g_alt = float(0.0)
589
+ prefix_alt = LAGRANGE_SCALE[node_all[0]]
590
+ for k in range(ORDER_PLUS_ONE):
591
+ if k != node_all[0]:
592
+ delta_alt = local_coords[0] - LOBATTO_COORDS[k]
593
+ w_alt *= delta_alt
594
+ g_alt = g_alt * delta_alt + prefix_alt
595
+ prefix_alt *= delta_alt
596
+
597
+ local_grad = wp.vec3(g_alt * w_long * w_lat, w_alt * g_long * w_lat, w_alt * w_long * g_lat)
598
+
599
+ return Grid3D._local_to_world(axis, local_grad)
600
+
601
+ return element_inner_weight_gradient
602
+
603
+ def element_node_tets(self):
604
+ from warp.fem.utils import grid_to_tets
605
+
606
+ if self.ORDER == 2:
607
+ element_tets = np.array(
608
+ [
609
+ [0, 8, 9, 10],
610
+ [1, 11, 10, 15],
611
+ [2, 9, 14, 13],
612
+ [3, 15, 13, 17],
613
+ [4, 12, 8, 16],
614
+ [5, 18, 16, 11],
615
+ [6, 14, 12, 19],
616
+ [7, 19, 18, 17],
617
+ [16, 12, 18, 11],
618
+ [8, 16, 12, 11],
619
+ [12, 19, 18, 14],
620
+ [14, 19, 17, 18],
621
+ [10, 9, 15, 8],
622
+ [10, 8, 11, 15],
623
+ [9, 13, 15, 14],
624
+ [13, 14, 17, 15],
625
+ ]
626
+ )
627
+
628
+ middle_hex = np.array([8, 11, 9, 15, 12, 18, 14, 17])
629
+ middle_tets = middle_hex[grid_to_tets(1, 1, 1)]
630
+
631
+ return np.concatenate((element_tets, middle_tets))
632
+
633
+ raise NotImplementedError()
634
+
635
+
636
+ class CubeNonConformingPolynomialShapeFunctions:
637
+ # embeds the largest regular tet centered at (0.5, 0.5, 0.5) into the reference cube
638
+
639
+ _tet_height = 2.0 / 3.0
640
+ _tet_side = math.sqrt(3.0 / 2.0) * _tet_height
641
+ _tet_face_height = math.sqrt(3.0) / 2.0 * _tet_side
642
+
643
+ _tet_to_cube = np.array(
644
+ [
645
+ [_tet_side, _tet_side / 2.0, _tet_side / 2.0],
646
+ [0.0, _tet_face_height, _tet_face_height / 3.0],
647
+ [0.0, 0.0, _tet_height],
648
+ ]
649
+ )
650
+
651
+ _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))
652
+
653
+ def __init__(self, degree: int):
654
+ self._tet_shape = TetrahedronPolynomialShapeFunctions(degree=degree)
655
+ self.ORDER = self._tet_shape.ORDER
656
+ self.NODES_PER_ELEMENT = self._tet_shape.NODES_PER_ELEMENT
657
+
658
+ self.element_node_tets = self._tet_shape.element_node_tets
659
+
660
+ @property
661
+ def name(self) -> str:
662
+ return f"Cube_P{self.ORDER}d"
663
+
664
+ def make_node_coords_in_element(self):
665
+ node_coords_in_tet = self._tet_shape.make_node_coords_in_element()
666
+
667
+ TET_TO_CUBE = wp.constant(wp.mat33(self._tet_to_cube))
668
+
669
+ @cache.dynamic_func(suffix=self.name)
670
+ def node_coords_in_element(
671
+ node_index_in_elt: int,
672
+ ):
673
+ tet_coords = node_coords_in_tet(node_index_in_elt)
674
+ return TET_TO_CUBE * tet_coords + CubeNonConformingPolynomialShapeFunctions._TET_OFFSET
675
+
676
+ return node_coords_in_element
677
+
678
+ def make_node_quadrature_weight(self):
679
+ NODES_PER_ELEMENT = self.NODES_PER_ELEMENT
680
+
681
+ @cache.dynamic_func(suffix=self.name)
682
+ def node_uniform_quadrature_weight(
683
+ node_index_in_elt: int,
684
+ ):
685
+ return 1.0 / float(NODES_PER_ELEMENT)
686
+
687
+ return node_uniform_quadrature_weight
688
+
689
+ def make_trace_node_quadrature_weight(self):
690
+ # Non-conforming, zero measure on sides
691
+
692
+ @wp.func
693
+ def zero(node_index_in_elt: int):
694
+ return 0.0
695
+
696
+ return zero
697
+
698
+ def make_element_inner_weight(self):
699
+ tet_inner_weight = self._tet_shape.make_element_inner_weight()
700
+
701
+ CUBE_TO_TET = wp.constant(wp.mat33(np.linalg.inv(self._tet_to_cube)))
702
+
703
+ @cache.dynamic_func(suffix=self.name)
704
+ def element_inner_weight(
705
+ coords: Coords,
706
+ node_index_in_elt: int,
707
+ ):
708
+ tet_coords = CUBE_TO_TET * (coords - CubeNonConformingPolynomialShapeFunctions._TET_OFFSET)
709
+
710
+ return tet_inner_weight(tet_coords, node_index_in_elt)
711
+
712
+ return element_inner_weight
713
+
714
+ def make_element_inner_weight_gradient(self):
715
+ tet_inner_weight_gradient = self._tet_shape.make_element_inner_weight_gradient()
716
+
717
+ CUBE_TO_TET = wp.constant(wp.mat33(np.linalg.inv(self._tet_to_cube)))
718
+
719
+ @cache.dynamic_func(suffix=self.name)
720
+ def element_inner_weight_gradient(
721
+ coords: Coords,
722
+ node_index_in_elt: int,
723
+ ):
724
+ tet_coords = CUBE_TO_TET * (coords - CubeNonConformingPolynomialShapeFunctions._TET_OFFSET)
725
+ grad = tet_inner_weight_gradient(tet_coords, node_index_in_elt)
726
+ return wp.transpose(CUBE_TO_TET) * grad
727
+
728
+ return element_inner_weight_gradient