warp-lang 1.4.1__py3-none-manylinux2014_x86_64.whl → 1.5.0__py3-none-manylinux2014_x86_64.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 (164) hide show
  1. warp/__init__.py +4 -0
  2. warp/autograd.py +43 -8
  3. warp/bin/warp-clang.so +0 -0
  4. warp/bin/warp.so +0 -0
  5. warp/build.py +21 -2
  6. warp/build_dll.py +23 -6
  7. warp/builtins.py +1920 -111
  8. warp/codegen.py +186 -62
  9. warp/config.py +2 -2
  10. warp/context.py +322 -73
  11. warp/examples/assets/pixel.jpg +0 -0
  12. warp/examples/benchmarks/benchmark_cloth_paddle.py +86 -0
  13. warp/examples/benchmarks/benchmark_gemm.py +121 -0
  14. warp/examples/benchmarks/benchmark_interop_paddle.py +158 -0
  15. warp/examples/benchmarks/benchmark_tile.py +179 -0
  16. warp/examples/core/example_dem.py +2 -1
  17. warp/examples/core/example_mesh_intersect.py +3 -3
  18. warp/examples/fem/example_adaptive_grid.py +37 -10
  19. warp/examples/fem/example_apic_fluid.py +3 -2
  20. warp/examples/fem/example_convection_diffusion_dg.py +4 -5
  21. warp/examples/fem/example_deformed_geometry.py +1 -1
  22. warp/examples/fem/example_diffusion_3d.py +47 -4
  23. warp/examples/fem/example_distortion_energy.py +220 -0
  24. warp/examples/fem/example_magnetostatics.py +127 -85
  25. warp/examples/fem/example_nonconforming_contact.py +5 -5
  26. warp/examples/fem/example_stokes.py +3 -1
  27. warp/examples/fem/example_streamlines.py +12 -19
  28. warp/examples/fem/utils.py +38 -15
  29. warp/examples/optim/example_walker.py +2 -2
  30. warp/examples/sim/example_cloth.py +2 -25
  31. warp/examples/sim/example_jacobian_ik.py +6 -2
  32. warp/examples/sim/example_quadruped.py +2 -1
  33. warp/examples/tile/example_tile_convolution.py +58 -0
  34. warp/examples/tile/example_tile_fft.py +47 -0
  35. warp/examples/tile/example_tile_filtering.py +105 -0
  36. warp/examples/tile/example_tile_matmul.py +79 -0
  37. warp/examples/tile/example_tile_mlp.py +375 -0
  38. warp/fem/__init__.py +8 -0
  39. warp/fem/cache.py +16 -12
  40. warp/fem/dirichlet.py +1 -1
  41. warp/fem/domain.py +44 -1
  42. warp/fem/field/__init__.py +1 -2
  43. warp/fem/field/field.py +31 -19
  44. warp/fem/field/nodal_field.py +101 -49
  45. warp/fem/field/virtual.py +794 -0
  46. warp/fem/geometry/__init__.py +2 -2
  47. warp/fem/geometry/deformed_geometry.py +3 -105
  48. warp/fem/geometry/element.py +13 -0
  49. warp/fem/geometry/geometry.py +165 -5
  50. warp/fem/geometry/grid_2d.py +3 -6
  51. warp/fem/geometry/grid_3d.py +31 -28
  52. warp/fem/geometry/hexmesh.py +3 -46
  53. warp/fem/geometry/nanogrid.py +3 -2
  54. warp/fem/geometry/{quadmesh_2d.py → quadmesh.py} +280 -159
  55. warp/fem/geometry/tetmesh.py +2 -43
  56. warp/fem/geometry/{trimesh_2d.py → trimesh.py} +354 -186
  57. warp/fem/integrate.py +683 -261
  58. warp/fem/linalg.py +404 -0
  59. warp/fem/operator.py +101 -18
  60. warp/fem/polynomial.py +5 -5
  61. warp/fem/quadrature/quadrature.py +45 -21
  62. warp/fem/space/__init__.py +45 -11
  63. warp/fem/space/basis_function_space.py +451 -0
  64. warp/fem/space/basis_space.py +58 -11
  65. warp/fem/space/function_space.py +146 -5
  66. warp/fem/space/grid_2d_function_space.py +80 -66
  67. warp/fem/space/grid_3d_function_space.py +113 -68
  68. warp/fem/space/hexmesh_function_space.py +96 -108
  69. warp/fem/space/nanogrid_function_space.py +62 -110
  70. warp/fem/space/quadmesh_function_space.py +208 -0
  71. warp/fem/space/shape/__init__.py +45 -7
  72. warp/fem/space/shape/cube_shape_function.py +328 -54
  73. warp/fem/space/shape/shape_function.py +10 -1
  74. warp/fem/space/shape/square_shape_function.py +328 -60
  75. warp/fem/space/shape/tet_shape_function.py +269 -19
  76. warp/fem/space/shape/triangle_shape_function.py +238 -19
  77. warp/fem/space/tetmesh_function_space.py +69 -37
  78. warp/fem/space/topology.py +38 -0
  79. warp/fem/space/trimesh_function_space.py +179 -0
  80. warp/fem/utils.py +6 -331
  81. warp/jax_experimental.py +3 -1
  82. warp/native/array.h +55 -40
  83. warp/native/builtin.h +124 -43
  84. warp/native/bvh.h +4 -0
  85. warp/native/coloring.cpp +600 -0
  86. warp/native/cuda_util.cpp +14 -0
  87. warp/native/cuda_util.h +2 -1
  88. warp/native/fabric.h +8 -0
  89. warp/native/hashgrid.h +4 -0
  90. warp/native/marching.cu +8 -0
  91. warp/native/mat.h +14 -3
  92. warp/native/mathdx.cpp +59 -0
  93. warp/native/mesh.h +4 -0
  94. warp/native/range.h +13 -1
  95. warp/native/reduce.cpp +9 -1
  96. warp/native/reduce.cu +7 -0
  97. warp/native/runlength_encode.cpp +9 -1
  98. warp/native/runlength_encode.cu +7 -1
  99. warp/native/scan.cpp +8 -0
  100. warp/native/scan.cu +8 -0
  101. warp/native/scan.h +8 -1
  102. warp/native/sparse.cpp +8 -0
  103. warp/native/sparse.cu +8 -0
  104. warp/native/temp_buffer.h +7 -0
  105. warp/native/tile.h +1857 -0
  106. warp/native/tile_gemm.h +341 -0
  107. warp/native/tile_reduce.h +210 -0
  108. warp/native/volume_builder.cu +8 -0
  109. warp/native/volume_builder.h +8 -0
  110. warp/native/warp.cpp +10 -2
  111. warp/native/warp.cu +369 -15
  112. warp/native/warp.h +12 -2
  113. warp/optim/adam.py +39 -4
  114. warp/paddle.py +29 -12
  115. warp/render/render_opengl.py +137 -65
  116. warp/sim/graph_coloring.py +292 -0
  117. warp/sim/integrator_euler.py +4 -2
  118. warp/sim/integrator_featherstone.py +115 -44
  119. warp/sim/integrator_vbd.py +6 -0
  120. warp/sim/model.py +90 -17
  121. warp/stubs.py +651 -85
  122. warp/tape.py +12 -7
  123. warp/tests/assets/pixel.npy +0 -0
  124. warp/tests/aux_test_instancing_gc.py +18 -0
  125. warp/tests/test_array.py +207 -48
  126. warp/tests/test_closest_point_edge_edge.py +8 -8
  127. warp/tests/test_codegen.py +120 -1
  128. warp/tests/test_codegen_instancing.py +30 -0
  129. warp/tests/test_collision.py +110 -0
  130. warp/tests/test_coloring.py +241 -0
  131. warp/tests/test_context.py +34 -0
  132. warp/tests/test_examples.py +18 -4
  133. warp/tests/test_fabricarray.py +33 -0
  134. warp/tests/test_fem.py +453 -113
  135. warp/tests/test_func.py +48 -1
  136. warp/tests/test_generics.py +52 -0
  137. warp/tests/test_iter.py +68 -0
  138. warp/tests/test_mat_scalar_ops.py +1 -1
  139. warp/tests/test_mesh_query_point.py +5 -4
  140. warp/tests/test_module_hashing.py +23 -0
  141. warp/tests/test_paddle.py +27 -87
  142. warp/tests/test_print.py +191 -1
  143. warp/tests/test_spatial.py +1 -1
  144. warp/tests/test_tile.py +700 -0
  145. warp/tests/test_tile_mathdx.py +144 -0
  146. warp/tests/test_tile_mlp.py +383 -0
  147. warp/tests/test_tile_reduce.py +374 -0
  148. warp/tests/test_tile_shared_memory.py +190 -0
  149. warp/tests/test_vbd.py +12 -20
  150. warp/tests/test_volume.py +43 -0
  151. warp/tests/unittest_suites.py +23 -2
  152. warp/tests/unittest_utils.py +4 -0
  153. warp/types.py +339 -73
  154. warp/utils.py +22 -1
  155. {warp_lang-1.4.1.dist-info → warp_lang-1.5.0.dist-info}/METADATA +33 -7
  156. {warp_lang-1.4.1.dist-info → warp_lang-1.5.0.dist-info}/RECORD +159 -132
  157. {warp_lang-1.4.1.dist-info → warp_lang-1.5.0.dist-info}/WHEEL +1 -1
  158. warp/fem/field/test.py +0 -180
  159. warp/fem/field/trial.py +0 -183
  160. warp/fem/space/collocated_function_space.py +0 -102
  161. warp/fem/space/quadmesh_2d_function_space.py +0 -261
  162. warp/fem/space/trimesh_2d_function_space.py +0 -153
  163. {warp_lang-1.4.1.dist-info → warp_lang-1.5.0.dist-info}/LICENSE.md +0 -0
  164. {warp_lang-1.4.1.dist-info → warp_lang-1.5.0.dist-info}/top_level.txt +0 -0
@@ -3,14 +3,9 @@ from typing import Union
3
3
  import warp as wp
4
4
  from warp.fem import cache
5
5
  from warp.fem.geometry import AdaptiveNanogrid, Nanogrid
6
- from warp.fem.polynomial import is_closed
7
6
  from warp.fem.types import ElementIndex
8
7
 
9
- from .shape import (
10
- CubeSerendipityShapeFunctions,
11
- CubeTripolynomialShapeFunctions,
12
- ShapeFunction,
13
- )
8
+ from .shape import CubeShapeFunction
14
9
  from .topology import SpaceTopology, forward_base_topology
15
10
 
16
11
 
@@ -31,19 +26,17 @@ class NanogridSpaceTopology(SpaceTopology):
31
26
  def __init__(
32
27
  self,
33
28
  grid: Union[Nanogrid, AdaptiveNanogrid],
34
- shape: ShapeFunction,
35
- need_edge_indices: bool = True,
36
- need_face_indices: bool = True,
29
+ shape: CubeShapeFunction,
37
30
  ):
38
- if not is_closed(shape.family):
39
- raise ValueError("A closed polynomial family is required to define a continuous function space")
40
-
31
+ self._shape = shape
41
32
  super().__init__(grid, shape.NODES_PER_ELEMENT)
42
33
  self._grid = grid
43
- self._shape = shape
44
34
 
45
35
  self._vertex_grid = grid.vertex_grid.id
46
36
 
37
+ need_edge_indices = shape.EDGE_NODE_COUNT > 0
38
+ need_face_indices = shape.FACE_NODE_COUNT > 0
39
+
47
40
  if isinstance(grid, Nanogrid):
48
41
  self._edge_grid = grid.edge_grid.id if need_edge_indices else -1
49
42
  self._face_grid = grid.face_grid.id if need_face_indices else -1
@@ -55,6 +48,12 @@ class NanogridSpaceTopology(SpaceTopology):
55
48
  self._edge_count = grid.stacked_edge_count() if need_edge_indices else 0
56
49
  self._face_count = grid.stacked_face_count() if need_face_indices else 0
57
50
 
51
+ self.element_node_index = self._make_element_node_index()
52
+
53
+ @property
54
+ def name(self):
55
+ return f"{self.geometry.name}_{self._shape.name}"
56
+
58
57
  @cache.cached_arg_value
59
58
  def topo_arg_value(self, device):
60
59
  arg = NanogridTopologyArg()
@@ -97,51 +96,19 @@ class NanogridSpaceTopology(SpaceTopology):
97
96
 
98
97
  return element_node_index_adaptive
99
98
 
100
-
101
- @wp.func
102
- def _cell_vertex_coord(cell_ijk: wp.vec3i, cell_level: int, n: int):
103
- return cell_ijk + AdaptiveNanogrid.fine_ijk(wp.vec3i((n & 4) >> 2, (n & 2) >> 1, n & 1), cell_level)
104
-
105
-
106
- @wp.func
107
- def _cell_edge_coord(cell_ijk: wp.vec3i, cell_level: int, axis: int, offset: int):
108
- e_ijk = AdaptiveNanogrid.coarse_ijk(cell_ijk, cell_level)
109
- e_ijk[(axis + 1) % 3] += offset >> 1
110
- e_ijk[(axis + 2) % 3] += offset & 1
111
- return AdaptiveNanogrid.encode_axis_and_level(e_ijk, axis, cell_level)
112
-
113
-
114
- @wp.func
115
- def _cell_face_coord(cell_ijk: wp.vec3i, cell_level: int, axis: int, offset: int):
116
- f_ijk = AdaptiveNanogrid.coarse_ijk(cell_ijk, cell_level)
117
- f_ijk[axis] += offset
118
- return AdaptiveNanogrid.encode_axis_and_level(f_ijk, axis, cell_level)
119
-
120
-
121
- class NanogridTripolynomialSpaceTopology(NanogridSpaceTopology):
122
- def __init__(self, grid: Union[Nanogrid, AdaptiveNanogrid], shape: CubeTripolynomialShapeFunctions):
123
- super().__init__(grid, shape, need_edge_indices=shape.ORDER >= 2, need_face_indices=shape.ORDER >= 2)
124
-
125
- self.element_node_index = self._make_element_node_index()
126
-
127
99
  def node_count(self) -> int:
128
- ORDER = self._shape.ORDER
129
- INTERIOR_NODES_PER_EDGE = max(0, ORDER - 1)
130
- INTERIOR_NODES_PER_FACE = INTERIOR_NODES_PER_EDGE**2
131
- INTERIOR_NODES_PER_CELL = INTERIOR_NODES_PER_EDGE**3
132
-
133
100
  return (
134
- self._grid.vertex_count()
135
- + self._edge_count * INTERIOR_NODES_PER_EDGE
136
- + self._face_count * INTERIOR_NODES_PER_FACE
137
- + self._grid.cell_count() * INTERIOR_NODES_PER_CELL
101
+ self._grid.vertex_count() * self._shape.VERTEX_NODE_COUNT
102
+ + self._edge_count * self._shape.EDGE_NODE_COUNT
103
+ + self._face_count * self._shape.FACE_NODE_COUNT
104
+ + self._grid.cell_count() * self._shape.INTERIOR_NODE_COUNT
138
105
  )
139
106
 
140
107
  def _make_element_node_index_generic(self):
141
- ORDER = self._shape.ORDER
142
- INTERIOR_NODES_PER_EDGE = wp.constant(max(0, ORDER - 1))
143
- INTERIOR_NODES_PER_FACE = wp.constant(INTERIOR_NODES_PER_EDGE**2)
144
- INTERIOR_NODES_PER_CELL = wp.constant(INTERIOR_NODES_PER_EDGE**3)
108
+ VERTEX_NODE_COUNT = self._shape.VERTEX_NODE_COUNT
109
+ EDGE_NODE_COUNT = self._shape.EDGE_NODE_COUNT
110
+ FACE_NODE_COUNT = self._shape.FACE_NODE_COUNT
111
+ INTERIOR_NODE_COUNT = self._shape.INTERIOR_NODE_COUNT
145
112
 
146
113
  @cache.dynamic_func(suffix=self.name)
147
114
  def element_node_index_generic(
@@ -153,82 +120,67 @@ class NanogridTripolynomialSpaceTopology(NanogridSpaceTopology):
153
120
  ):
154
121
  node_type, type_instance, type_index = self._shape.node_type_and_type_index(node_index_in_elt)
155
122
 
156
- if node_type == CubeTripolynomialShapeFunctions.VERTEX:
157
- n_ijk = _cell_vertex_coord(ijk, level, type_instance)
158
- return wp.volume_lookup_index(topo_arg.vertex_grid, n_ijk[0], n_ijk[1], n_ijk[2])
123
+ if wp.static(VERTEX_NODE_COUNT > 0):
124
+ if node_type == CubeShapeFunction.VERTEX:
125
+ n_ijk = _cell_vertex_coord(ijk, level, type_instance)
126
+ return (
127
+ wp.volume_lookup_index(topo_arg.vertex_grid, n_ijk[0], n_ijk[1], n_ijk[2]) * VERTEX_NODE_COUNT
128
+ + type_index
129
+ )
159
130
 
160
- offset = topo_arg.vertex_count
131
+ offset = topo_arg.vertex_count * VERTEX_NODE_COUNT
161
132
 
162
- if node_type == CubeTripolynomialShapeFunctions.EDGE:
163
- axis = type_instance >> 2
164
- node_offset = type_instance & 3
133
+ if wp.static(EDGE_NODE_COUNT > 0):
134
+ if node_type == CubeShapeFunction.EDGE:
135
+ axis = type_instance >> 2
136
+ node_offset = type_instance & 3
165
137
 
166
- n_ijk = _cell_edge_coord(ijk, level, axis, node_offset)
138
+ n_ijk = _cell_edge_coord(ijk, level, axis, node_offset)
167
139
 
168
- edge_index = wp.volume_lookup_index(topo_arg.edge_grid, n_ijk[0], n_ijk[1], n_ijk[2])
169
- return offset + INTERIOR_NODES_PER_EDGE * edge_index + type_index
140
+ edge_index = wp.volume_lookup_index(topo_arg.edge_grid, n_ijk[0], n_ijk[1], n_ijk[2])
141
+ return offset + EDGE_NODE_COUNT * edge_index + type_index
170
142
 
171
- offset += INTERIOR_NODES_PER_EDGE * topo_arg.edge_count
143
+ offset += EDGE_NODE_COUNT * topo_arg.edge_count
172
144
 
173
- if node_type == CubeTripolynomialShapeFunctions.FACE:
174
- axis = type_instance >> 1
175
- node_offset = type_instance & 1
145
+ if wp.static(FACE_NODE_COUNT > 0):
146
+ if node_type == CubeShapeFunction.FACE:
147
+ axis = type_instance >> 1
148
+ node_offset = type_instance & 1
176
149
 
177
- n_ijk = _cell_face_coord(ijk, level, axis, node_offset)
150
+ n_ijk = _cell_face_coord(ijk, level, axis, node_offset)
178
151
 
179
- face_index = wp.volume_lookup_index(topo_arg.face_grid, n_ijk[0], n_ijk[1], n_ijk[2])
180
- return offset + INTERIOR_NODES_PER_FACE * face_index + type_index
152
+ face_index = wp.volume_lookup_index(topo_arg.face_grid, n_ijk[0], n_ijk[1], n_ijk[2])
153
+ return offset + FACE_NODE_COUNT * face_index + type_index
181
154
 
182
- offset += INTERIOR_NODES_PER_FACE * topo_arg.face_count
155
+ offset += FACE_NODE_COUNT * topo_arg.face_count
183
156
 
184
- return offset + INTERIOR_NODES_PER_CELL * element_index + type_index
157
+ return offset + INTERIOR_NODE_COUNT * element_index + type_index
185
158
 
186
159
  return element_node_index_generic
187
160
 
188
161
 
189
- class NanogridSerendipitySpaceTopology(NanogridSpaceTopology):
190
- def __init__(self, grid: Nanogrid, shape: CubeSerendipityShapeFunctions):
191
- super().__init__(grid, shape, need_edge_indices=True, need_face_indices=False)
192
-
193
- self.element_node_index = self._make_element_node_index()
194
-
195
- def node_count(self) -> int:
196
- return self.geometry.vertex_count() + (self._shape.ORDER - 1) * self._edge_count
197
-
198
- def _make_element_node_index_generic(self):
199
- ORDER = self._shape.ORDER
200
-
201
- @cache.dynamic_func(suffix=self.name)
202
- def element_node_index_generic(
203
- topo_arg: NanogridTopologyArg,
204
- element_index: ElementIndex,
205
- node_index_in_elt: int,
206
- ijk: wp.vec3i,
207
- level: int,
208
- ):
209
- node_type, type_index = self._shape.node_type_and_type_index(node_index_in_elt)
210
-
211
- if node_type == CubeSerendipityShapeFunctions.VERTEX:
212
- n_ijk = _cell_vertex_coord(ijk, level, type_index)
213
- return wp.volume_lookup_index(topo_arg.vertex_grid, n_ijk[0], n_ijk[1], n_ijk[2])
214
-
215
- type_instance, index_in_edge = CubeSerendipityShapeFunctions._cube_edge_index(node_type, type_index)
216
- axis = type_instance >> 2
217
- node_offset = type_instance & 3
162
+ @wp.func
163
+ def _cell_vertex_coord(cell_ijk: wp.vec3i, cell_level: int, n: int):
164
+ return cell_ijk + AdaptiveNanogrid.fine_ijk(wp.vec3i((n & 4) >> 2, (n & 2) >> 1, n & 1), cell_level)
218
165
 
219
- n_ijk = _cell_edge_coord(ijk, level, axis, node_offset)
220
166
 
221
- edge_index = wp.volume_lookup_index(topo_arg.edge_grid, n_ijk[0], n_ijk[1], n_ijk[2])
222
- return topo_arg.vertex_count + (ORDER - 1) * edge_index + index_in_edge
167
+ @wp.func
168
+ def _cell_edge_coord(cell_ijk: wp.vec3i, cell_level: int, axis: int, offset: int):
169
+ e_ijk = AdaptiveNanogrid.coarse_ijk(cell_ijk, cell_level)
170
+ e_ijk[(axis + 1) % 3] += offset >> 1
171
+ e_ijk[(axis + 2) % 3] += offset & 1
172
+ return AdaptiveNanogrid.encode_axis_and_level(e_ijk, axis, cell_level)
223
173
 
224
- return element_node_index_generic
225
174
 
175
+ @wp.func
176
+ def _cell_face_coord(cell_ijk: wp.vec3i, cell_level: int, axis: int, offset: int):
177
+ f_ijk = AdaptiveNanogrid.coarse_ijk(cell_ijk, cell_level)
178
+ f_ijk[axis] += offset
179
+ return AdaptiveNanogrid.encode_axis_and_level(f_ijk, axis, cell_level)
226
180
 
227
- def make_nanogrid_space_topology(grid: Union[Nanogrid, AdaptiveNanogrid], shape: ShapeFunction):
228
- if isinstance(shape, CubeSerendipityShapeFunctions):
229
- return forward_base_topology(NanogridSerendipitySpaceTopology, grid, shape)
230
181
 
231
- if isinstance(shape, CubeTripolynomialShapeFunctions):
232
- return forward_base_topology(NanogridTripolynomialSpaceTopology, grid, shape)
182
+ def make_nanogrid_space_topology(grid: Union[Nanogrid, AdaptiveNanogrid], shape: CubeShapeFunction):
183
+ if isinstance(shape, CubeShapeFunction):
184
+ return forward_base_topology(NanogridSpaceTopology, grid, shape)
233
185
 
234
186
  raise ValueError(f"Unsupported shape function {shape.name}")
@@ -0,0 +1,208 @@
1
+ import warp as wp
2
+ from warp.fem import cache
3
+ from warp.fem.geometry import Quadmesh2D
4
+ from warp.fem.polynomial import is_closed
5
+ from warp.fem.types import NULL_NODE_INDEX, ElementIndex
6
+
7
+ from .shape import SquareShapeFunction
8
+ from .topology import SpaceTopology, forward_base_topology
9
+
10
+
11
+ @wp.struct
12
+ class Quadmesh2DTopologyArg:
13
+ edge_vertex_indices: wp.array(dtype=wp.vec2i)
14
+ quad_edge_indices: wp.array2d(dtype=int)
15
+
16
+ vertex_count: int
17
+ edge_count: int
18
+ cell_count: int
19
+
20
+
21
+ class QuadmeshSpaceTopology(SpaceTopology):
22
+ TopologyArg = Quadmesh2DTopologyArg
23
+
24
+ def __init__(self, mesh: Quadmesh2D, shape: SquareShapeFunction):
25
+ if shape.value == SquareShapeFunction.Value.Scalar and not is_closed(shape.family):
26
+ raise ValueError("A closed polynomial family is required to define a continuous function space")
27
+
28
+ self._shape = shape
29
+ super().__init__(mesh, shape.NODES_PER_ELEMENT)
30
+ self._mesh = mesh
31
+
32
+ self._compute_quad_edge_indices()
33
+ self.element_node_index = self._make_element_node_index()
34
+ self.element_node_sign = self._make_element_node_sign()
35
+
36
+ @property
37
+ def name(self):
38
+ return f"{self.geometry.name}_{self._shape.name}"
39
+
40
+ @cache.cached_arg_value
41
+ def topo_arg_value(self, device):
42
+ arg = Quadmesh2DTopologyArg()
43
+ arg.quad_edge_indices = self._quad_edge_indices.to(device)
44
+ arg.edge_vertex_indices = self._mesh.edge_vertex_indices.to(device)
45
+
46
+ arg.vertex_count = self._mesh.vertex_count()
47
+ arg.edge_count = self._mesh.side_count()
48
+ arg.cell_count = self._mesh.cell_count()
49
+ return arg
50
+
51
+ def _compute_quad_edge_indices(self):
52
+ self._quad_edge_indices = wp.empty(
53
+ dtype=int, device=self._mesh.quad_vertex_indices.device, shape=(self._mesh.cell_count(), 4)
54
+ )
55
+
56
+ wp.launch(
57
+ kernel=QuadmeshSpaceTopology._compute_quad_edge_indices_kernel,
58
+ dim=self._mesh.edge_quad_indices.shape,
59
+ device=self._mesh.quad_vertex_indices.device,
60
+ inputs=[
61
+ self._mesh.edge_quad_indices,
62
+ self._mesh.edge_vertex_indices,
63
+ self._mesh.quad_vertex_indices,
64
+ self._quad_edge_indices,
65
+ ],
66
+ )
67
+
68
+ @wp.func
69
+ def _find_edge_index_in_quad(
70
+ edge_vtx: wp.vec2i,
71
+ quad_vtx: wp.vec4i,
72
+ ):
73
+ for k in range(3):
74
+ if (edge_vtx[0] == quad_vtx[k] and edge_vtx[1] == quad_vtx[k + 1]) or (
75
+ edge_vtx[1] == quad_vtx[k] and edge_vtx[0] == quad_vtx[k + 1]
76
+ ):
77
+ return k
78
+ return 3
79
+
80
+ @wp.kernel
81
+ def _compute_quad_edge_indices_kernel(
82
+ edge_quad_indices: wp.array(dtype=wp.vec2i),
83
+ edge_vertex_indices: wp.array(dtype=wp.vec2i),
84
+ quad_vertex_indices: wp.array2d(dtype=int),
85
+ quad_edge_indices: wp.array2d(dtype=int),
86
+ ):
87
+ e = wp.tid()
88
+
89
+ edge_vtx = edge_vertex_indices[e]
90
+ edge_quads = edge_quad_indices[e]
91
+
92
+ q0 = edge_quads[0]
93
+ q0_vtx = wp.vec4i(
94
+ quad_vertex_indices[q0, 0],
95
+ quad_vertex_indices[q0, 1],
96
+ quad_vertex_indices[q0, 2],
97
+ quad_vertex_indices[q0, 3],
98
+ )
99
+ q0_edge = QuadmeshSpaceTopology._find_edge_index_in_quad(edge_vtx, q0_vtx)
100
+ quad_edge_indices[q0, q0_edge] = e
101
+
102
+ q1 = edge_quads[1]
103
+ if q1 != q0:
104
+ t1_vtx = wp.vec4i(
105
+ quad_vertex_indices[q1, 0],
106
+ quad_vertex_indices[q1, 1],
107
+ quad_vertex_indices[q1, 2],
108
+ quad_vertex_indices[q1, 3],
109
+ )
110
+ t1_edge = QuadmeshSpaceTopology._find_edge_index_in_quad(edge_vtx, t1_vtx)
111
+ quad_edge_indices[q1, t1_edge] = e
112
+
113
+ def node_count(self) -> int:
114
+ return (
115
+ self.geometry.vertex_count() * self._shape.VERTEX_NODE_COUNT
116
+ + self.geometry.side_count() * self._shape.EDGE_NODE_COUNT
117
+ + self.geometry.cell_count() * self._shape.INTERIOR_NODE_COUNT
118
+ )
119
+
120
+ def _make_element_node_index(self):
121
+ VERTEX_NODE_COUNT = self._shape.VERTEX_NODE_COUNT
122
+ EDGE_NODE_COUNT = self._shape.EDGE_NODE_COUNT
123
+ INTERIOR_NODE_COUNT = self._shape.INTERIOR_NODE_COUNT
124
+
125
+ SHAPE_TO_QUAD_IDX = wp.constant(wp.vec4i([0, 3, 1, 2]))
126
+
127
+ @cache.dynamic_func(suffix=self.name)
128
+ def element_node_index(
129
+ cell_arg: self._mesh.CellArg,
130
+ topo_arg: QuadmeshSpaceTopology.TopologyArg,
131
+ element_index: ElementIndex,
132
+ node_index_in_elt: int,
133
+ ):
134
+ node_type, type_instance, type_index = self._shape.node_type_and_type_index(node_index_in_elt)
135
+
136
+ if wp.static(VERTEX_NODE_COUNT > 0):
137
+ if node_type == SquareShapeFunction.VERTEX:
138
+ return (
139
+ cell_arg.topology.quad_vertex_indices[element_index, SHAPE_TO_QUAD_IDX[type_instance]]
140
+ * VERTEX_NODE_COUNT
141
+ + type_index
142
+ )
143
+
144
+ global_offset = topo_arg.vertex_count * VERTEX_NODE_COUNT
145
+
146
+ if wp.static(INTERIOR_NODE_COUNT > 0):
147
+ if node_type == SquareShapeFunction.INTERIOR:
148
+ return global_offset + element_index * INTERIOR_NODE_COUNT + type_index
149
+
150
+ global_offset += INTERIOR_NODE_COUNT * topo_arg.cell_count
151
+
152
+ if wp.static(EDGE_NODE_COUNT > 0):
153
+ # EDGE_X, EDGE_Y
154
+ side_start = wp.select(
155
+ node_type == SquareShapeFunction.EDGE_X,
156
+ wp.select(type_instance == 0, 1, 3),
157
+ wp.select(type_instance == 0, 2, 0),
158
+ )
159
+
160
+ side_index = topo_arg.quad_edge_indices[element_index, side_start]
161
+ local_vs = cell_arg.topology.quad_vertex_indices[element_index, side_start]
162
+ global_vs = topo_arg.edge_vertex_indices[side_index][0]
163
+
164
+ # Flip indexing direction
165
+ flipped = int(side_start >= 2) ^ int(local_vs != global_vs)
166
+ index_in_side = wp.select(flipped, type_index, EDGE_NODE_COUNT - 1 - type_index)
167
+
168
+ return global_offset + EDGE_NODE_COUNT * side_index + index_in_side
169
+
170
+ return NULL_NODE_INDEX # should never happen
171
+
172
+ return element_node_index
173
+
174
+ def _make_element_node_sign(self):
175
+ @cache.dynamic_func(suffix=self.name)
176
+ def element_node_sign(
177
+ cell_arg: self._mesh.CellArg,
178
+ topo_arg: QuadmeshSpaceTopology.TopologyArg,
179
+ element_index: ElementIndex,
180
+ node_index_in_elt: int,
181
+ ):
182
+ node_type, type_instance, type_index = self._shape.node_type_and_type_index(node_index_in_elt)
183
+
184
+ if node_type == SquareShapeFunction.EDGE_X or node_type == SquareShapeFunction.EDGE_Y:
185
+ side_start = wp.select(
186
+ node_type == SquareShapeFunction.EDGE_X,
187
+ wp.select(type_instance == 0, 1, 3),
188
+ wp.select(type_instance == 0, 2, 0),
189
+ )
190
+
191
+ side_index = topo_arg.quad_edge_indices[element_index, side_start]
192
+ local_vs = cell_arg.topology.quad_vertex_indices[element_index, side_start]
193
+ global_vs = topo_arg.edge_vertex_indices[side_index][0]
194
+
195
+ # Flip indexing direction
196
+ flipped = int(side_start >= 2) ^ int(local_vs != global_vs)
197
+ return wp.select(flipped, 1.0, -1.0)
198
+
199
+ return 1.0
200
+
201
+ return element_node_sign
202
+
203
+
204
+ def make_quadmesh_space_topology(mesh: Quadmesh2D, shape: SquareShapeFunction):
205
+ if isinstance(shape, SquareShapeFunction):
206
+ return forward_base_topology(QuadmeshSpaceTopology, mesh, shape)
207
+
208
+ raise ValueError(f"Unsupported shape function {shape.name}")
@@ -5,29 +5,51 @@ from warp.fem.geometry import element as _element
5
5
  from warp.fem.polynomial import Polynomial
6
6
 
7
7
  from .cube_shape_function import (
8
+ CubeNedelecFirstKindShapeFunctions,
8
9
  CubeNonConformingPolynomialShapeFunctions,
10
+ CubeRaviartThomasShapeFunctions,
9
11
  CubeSerendipityShapeFunctions,
12
+ CubeShapeFunction,
10
13
  CubeTripolynomialShapeFunctions,
11
14
  )
12
15
  from .shape_function import ConstantShapeFunction, ShapeFunction
13
16
  from .square_shape_function import (
14
17
  SquareBipolynomialShapeFunctions,
18
+ SquareNedelecFirstKindShapeFunctions,
15
19
  SquareNonConformingPolynomialShapeFunctions,
20
+ SquareRaviartThomasShapeFunctions,
16
21
  SquareSerendipityShapeFunctions,
22
+ SquareShapeFunction,
23
+ )
24
+ from .tet_shape_function import (
25
+ TetrahedronNedelecFirstKindShapeFunctions,
26
+ TetrahedronNonConformingPolynomialShapeFunctions,
27
+ TetrahedronPolynomialShapeFunctions,
28
+ TetrahedronRaviartThomasShapeFunctions,
29
+ TetrahedronShapeFunction,
30
+ )
31
+ from .triangle_shape_function import (
32
+ TriangleNedelecFirstKindShapeFunctions,
33
+ TriangleNonConformingPolynomialShapeFunctions,
34
+ TrianglePolynomialShapeFunctions,
35
+ TriangleRaviartThomasShapeFunctions,
36
+ TriangleShapeFunction,
17
37
  )
18
- from .tet_shape_function import TetrahedronNonConformingPolynomialShapeFunctions, TetrahedronPolynomialShapeFunctions
19
- from .triangle_shape_function import Triangle2DNonConformingPolynomialShapeFunctions, Triangle2DPolynomialShapeFunctions
20
38
 
21
39
 
22
40
  class ElementBasis(Enum):
23
41
  """Choice of basis function to equip individual elements"""
24
42
 
25
- LAGRANGE = 0
43
+ LAGRANGE = "P"
26
44
  """Lagrange basis functions :math:`P_k` for simplices, tensor products :math:`Q_k` for squares and cubes"""
27
- SERENDIPITY = 1
45
+ SERENDIPITY = "S"
28
46
  """Serendipity elements :math:`S_k`, corresponding to Lagrange nodes with interior points removed (for degree <= 3)"""
29
- NONCONFORMING_POLYNOMIAL = 2
47
+ NONCONFORMING_POLYNOMIAL = "dP"
30
48
  """Simplex Lagrange basis functions :math:`P_{kd}` embedded into non conforming reference elements (e.g. squares or cubes). Discontinuous only."""
49
+ NEDELEC_FIRST_KIND = "N1"
50
+ """Nédélec (first kind) H(curl) shape functions. Should be used with covariant function space."""
51
+ RAVIART_THOMAS = "RT"
52
+ """Raviart-Thomas H(div) shape functions. Should be used with contravariant function space."""
31
53
 
32
54
 
33
55
  def get_shape_function(
@@ -58,6 +80,10 @@ def get_shape_function(
58
80
  family = Polynomial.LOBATTO_GAUSS_LEGENDRE
59
81
 
60
82
  if isinstance(element, _element.Square):
83
+ if element_basis == ElementBasis.NEDELEC_FIRST_KIND:
84
+ return SquareNedelecFirstKindShapeFunctions(degree=degree)
85
+ if element_basis == ElementBasis.RAVIART_THOMAS:
86
+ return SquareRaviartThomasShapeFunctions(degree=degree)
61
87
  if element_basis == ElementBasis.NONCONFORMING_POLYNOMIAL:
62
88
  return SquareNonConformingPolynomialShapeFunctions(degree=degree)
63
89
  if element_basis == ElementBasis.SERENDIPITY and degree > 1:
@@ -65,14 +91,22 @@ def get_shape_function(
65
91
 
66
92
  return SquareBipolynomialShapeFunctions(degree=degree, family=family)
67
93
  if isinstance(element, _element.Triangle):
94
+ if element_basis == ElementBasis.NEDELEC_FIRST_KIND:
95
+ return TriangleNedelecFirstKindShapeFunctions(degree=degree)
96
+ if element_basis == ElementBasis.RAVIART_THOMAS:
97
+ return TriangleRaviartThomasShapeFunctions(degree=degree)
68
98
  if element_basis == ElementBasis.NONCONFORMING_POLYNOMIAL:
69
- return Triangle2DNonConformingPolynomialShapeFunctions(degree=degree)
99
+ return TriangleNonConformingPolynomialShapeFunctions(degree=degree)
70
100
  if element_basis == ElementBasis.SERENDIPITY and degree > 2:
71
101
  raise NotImplementedError("Serendipity variant not implemented yet for Triangle elements")
72
102
 
73
- return Triangle2DPolynomialShapeFunctions(degree=degree)
103
+ return TrianglePolynomialShapeFunctions(degree=degree)
74
104
 
75
105
  if isinstance(element, _element.Cube):
106
+ if element_basis == ElementBasis.NEDELEC_FIRST_KIND:
107
+ return CubeNedelecFirstKindShapeFunctions(degree=degree)
108
+ if element_basis == ElementBasis.RAVIART_THOMAS:
109
+ return CubeRaviartThomasShapeFunctions(degree=degree)
76
110
  if element_basis == ElementBasis.NONCONFORMING_POLYNOMIAL:
77
111
  return CubeNonConformingPolynomialShapeFunctions(degree=degree)
78
112
  if element_basis == ElementBasis.SERENDIPITY and degree > 1:
@@ -80,6 +114,10 @@ def get_shape_function(
80
114
 
81
115
  return CubeTripolynomialShapeFunctions(degree=degree, family=family)
82
116
  if isinstance(element, _element.Tetrahedron):
117
+ if element_basis == ElementBasis.NEDELEC_FIRST_KIND:
118
+ return TetrahedronNedelecFirstKindShapeFunctions(degree=degree)
119
+ if element_basis == ElementBasis.RAVIART_THOMAS:
120
+ return TetrahedronRaviartThomasShapeFunctions(degree=degree)
83
121
  if element_basis == ElementBasis.NONCONFORMING_POLYNOMIAL:
84
122
  return TetrahedronNonConformingPolynomialShapeFunctions(degree=degree)
85
123
  if element_basis == ElementBasis.SERENDIPITY and degree > 2: