warp-lang 1.2.1__py3-none-macosx_10_13_universal2.whl → 1.3.0__py3-none-macosx_10_13_universal2.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 (194) hide show
  1. warp/__init__.py +8 -6
  2. warp/autograd.py +823 -0
  3. warp/bin/libwarp-clang.dylib +0 -0
  4. warp/bin/libwarp.dylib +0 -0
  5. warp/build.py +6 -2
  6. warp/builtins.py +1410 -886
  7. warp/codegen.py +503 -166
  8. warp/config.py +48 -18
  9. warp/context.py +401 -199
  10. warp/dlpack.py +8 -0
  11. warp/examples/assets/bunny.usd +0 -0
  12. warp/examples/benchmarks/benchmark_cloth_warp.py +1 -1
  13. warp/examples/benchmarks/benchmark_interop_torch.py +158 -0
  14. warp/examples/benchmarks/benchmark_launches.py +1 -1
  15. warp/examples/core/example_cupy.py +78 -0
  16. warp/examples/fem/example_apic_fluid.py +17 -36
  17. warp/examples/fem/example_burgers.py +9 -18
  18. warp/examples/fem/example_convection_diffusion.py +7 -17
  19. warp/examples/fem/example_convection_diffusion_dg.py +27 -47
  20. warp/examples/fem/example_deformed_geometry.py +11 -22
  21. warp/examples/fem/example_diffusion.py +7 -18
  22. warp/examples/fem/example_diffusion_3d.py +24 -28
  23. warp/examples/fem/example_diffusion_mgpu.py +7 -14
  24. warp/examples/fem/example_magnetostatics.py +190 -0
  25. warp/examples/fem/example_mixed_elasticity.py +111 -80
  26. warp/examples/fem/example_navier_stokes.py +30 -34
  27. warp/examples/fem/example_nonconforming_contact.py +290 -0
  28. warp/examples/fem/example_stokes.py +17 -32
  29. warp/examples/fem/example_stokes_transfer.py +12 -21
  30. warp/examples/fem/example_streamlines.py +350 -0
  31. warp/examples/fem/utils.py +936 -0
  32. warp/fabric.py +5 -2
  33. warp/fem/__init__.py +13 -3
  34. warp/fem/cache.py +161 -11
  35. warp/fem/dirichlet.py +37 -28
  36. warp/fem/domain.py +105 -14
  37. warp/fem/field/__init__.py +14 -3
  38. warp/fem/field/field.py +454 -11
  39. warp/fem/field/nodal_field.py +33 -18
  40. warp/fem/geometry/deformed_geometry.py +50 -15
  41. warp/fem/geometry/hexmesh.py +12 -24
  42. warp/fem/geometry/nanogrid.py +106 -31
  43. warp/fem/geometry/quadmesh_2d.py +6 -11
  44. warp/fem/geometry/tetmesh.py +103 -61
  45. warp/fem/geometry/trimesh_2d.py +98 -47
  46. warp/fem/integrate.py +231 -186
  47. warp/fem/operator.py +14 -9
  48. warp/fem/quadrature/pic_quadrature.py +35 -9
  49. warp/fem/quadrature/quadrature.py +119 -32
  50. warp/fem/space/basis_space.py +98 -22
  51. warp/fem/space/collocated_function_space.py +3 -1
  52. warp/fem/space/function_space.py +7 -2
  53. warp/fem/space/grid_2d_function_space.py +3 -3
  54. warp/fem/space/grid_3d_function_space.py +4 -4
  55. warp/fem/space/hexmesh_function_space.py +3 -2
  56. warp/fem/space/nanogrid_function_space.py +12 -14
  57. warp/fem/space/partition.py +45 -47
  58. warp/fem/space/restriction.py +19 -16
  59. warp/fem/space/shape/cube_shape_function.py +91 -3
  60. warp/fem/space/shape/shape_function.py +7 -0
  61. warp/fem/space/shape/square_shape_function.py +32 -0
  62. warp/fem/space/shape/tet_shape_function.py +11 -7
  63. warp/fem/space/shape/triangle_shape_function.py +10 -1
  64. warp/fem/space/topology.py +116 -42
  65. warp/fem/types.py +8 -1
  66. warp/fem/utils.py +301 -83
  67. warp/native/array.h +16 -0
  68. warp/native/builtin.h +0 -15
  69. warp/native/cuda_util.cpp +14 -6
  70. warp/native/exports.h +1348 -1308
  71. warp/native/quat.h +79 -0
  72. warp/native/rand.h +27 -4
  73. warp/native/sparse.cpp +83 -81
  74. warp/native/sparse.cu +381 -453
  75. warp/native/vec.h +64 -0
  76. warp/native/volume.cpp +40 -49
  77. warp/native/volume_builder.cu +2 -3
  78. warp/native/volume_builder.h +12 -17
  79. warp/native/warp.cu +3 -3
  80. warp/native/warp.h +69 -59
  81. warp/render/render_opengl.py +17 -9
  82. warp/sim/articulation.py +117 -17
  83. warp/sim/collide.py +35 -29
  84. warp/sim/model.py +123 -18
  85. warp/sim/render.py +3 -1
  86. warp/sparse.py +867 -203
  87. warp/stubs.py +312 -541
  88. warp/tape.py +29 -1
  89. warp/tests/disabled_kinematics.py +1 -1
  90. warp/tests/test_adam.py +1 -1
  91. warp/tests/test_arithmetic.py +1 -1
  92. warp/tests/test_array.py +58 -1
  93. warp/tests/test_array_reduce.py +1 -1
  94. warp/tests/test_async.py +1 -1
  95. warp/tests/test_atomic.py +1 -1
  96. warp/tests/test_bool.py +1 -1
  97. warp/tests/test_builtins_resolution.py +1 -1
  98. warp/tests/test_bvh.py +6 -1
  99. warp/tests/test_closest_point_edge_edge.py +1 -1
  100. warp/tests/test_codegen.py +66 -1
  101. warp/tests/test_compile_consts.py +1 -1
  102. warp/tests/test_conditional.py +1 -1
  103. warp/tests/test_copy.py +1 -1
  104. warp/tests/test_ctypes.py +1 -1
  105. warp/tests/test_dense.py +1 -1
  106. warp/tests/test_devices.py +1 -1
  107. warp/tests/test_dlpack.py +1 -1
  108. warp/tests/test_examples.py +33 -4
  109. warp/tests/test_fabricarray.py +5 -2
  110. warp/tests/test_fast_math.py +1 -1
  111. warp/tests/test_fem.py +213 -6
  112. warp/tests/test_fp16.py +1 -1
  113. warp/tests/test_func.py +1 -1
  114. warp/tests/test_future_annotations.py +90 -0
  115. warp/tests/test_generics.py +1 -1
  116. warp/tests/test_grad.py +1 -1
  117. warp/tests/test_grad_customs.py +1 -1
  118. warp/tests/test_grad_debug.py +247 -0
  119. warp/tests/test_hash_grid.py +6 -1
  120. warp/tests/test_implicit_init.py +354 -0
  121. warp/tests/test_import.py +1 -1
  122. warp/tests/test_indexedarray.py +1 -1
  123. warp/tests/test_intersect.py +1 -1
  124. warp/tests/test_jax.py +1 -1
  125. warp/tests/test_large.py +1 -1
  126. warp/tests/test_launch.py +1 -1
  127. warp/tests/test_lerp.py +1 -1
  128. warp/tests/test_linear_solvers.py +1 -1
  129. warp/tests/test_lvalue.py +1 -1
  130. warp/tests/test_marching_cubes.py +5 -2
  131. warp/tests/test_mat.py +34 -35
  132. warp/tests/test_mat_lite.py +2 -1
  133. warp/tests/test_mat_scalar_ops.py +1 -1
  134. warp/tests/test_math.py +1 -1
  135. warp/tests/test_matmul.py +20 -16
  136. warp/tests/test_matmul_lite.py +1 -1
  137. warp/tests/test_mempool.py +1 -1
  138. warp/tests/test_mesh.py +5 -2
  139. warp/tests/test_mesh_query_aabb.py +1 -1
  140. warp/tests/test_mesh_query_point.py +1 -1
  141. warp/tests/test_mesh_query_ray.py +1 -1
  142. warp/tests/test_mlp.py +1 -1
  143. warp/tests/test_model.py +1 -1
  144. warp/tests/test_module_hashing.py +77 -1
  145. warp/tests/test_modules_lite.py +1 -1
  146. warp/tests/test_multigpu.py +1 -1
  147. warp/tests/test_noise.py +1 -1
  148. warp/tests/test_operators.py +1 -1
  149. warp/tests/test_options.py +1 -1
  150. warp/tests/test_overwrite.py +542 -0
  151. warp/tests/test_peer.py +1 -1
  152. warp/tests/test_pinned.py +1 -1
  153. warp/tests/test_print.py +1 -1
  154. warp/tests/test_quat.py +15 -1
  155. warp/tests/test_rand.py +1 -1
  156. warp/tests/test_reload.py +1 -1
  157. warp/tests/test_rounding.py +1 -1
  158. warp/tests/test_runlength_encode.py +1 -1
  159. warp/tests/test_scalar_ops.py +95 -0
  160. warp/tests/test_sim_grad.py +1 -1
  161. warp/tests/test_sim_kinematics.py +1 -1
  162. warp/tests/test_smoothstep.py +1 -1
  163. warp/tests/test_sparse.py +82 -15
  164. warp/tests/test_spatial.py +1 -1
  165. warp/tests/test_special_values.py +2 -11
  166. warp/tests/test_streams.py +11 -1
  167. warp/tests/test_struct.py +1 -1
  168. warp/tests/test_tape.py +1 -1
  169. warp/tests/test_torch.py +194 -1
  170. warp/tests/test_transient_module.py +1 -1
  171. warp/tests/test_types.py +1 -1
  172. warp/tests/test_utils.py +1 -1
  173. warp/tests/test_vec.py +15 -63
  174. warp/tests/test_vec_lite.py +2 -1
  175. warp/tests/test_vec_scalar_ops.py +122 -39
  176. warp/tests/test_verify_fp.py +1 -1
  177. warp/tests/test_volume.py +28 -2
  178. warp/tests/test_volume_write.py +1 -1
  179. warp/tests/unittest_serial.py +1 -1
  180. warp/tests/unittest_suites.py +9 -1
  181. warp/tests/walkthrough_debug.py +1 -1
  182. warp/thirdparty/unittest_parallel.py +2 -5
  183. warp/torch.py +103 -41
  184. warp/types.py +344 -227
  185. warp/utils.py +11 -2
  186. {warp_lang-1.2.1.dist-info → warp_lang-1.3.0.dist-info}/METADATA +99 -46
  187. warp_lang-1.3.0.dist-info/RECORD +368 -0
  188. warp/examples/fem/bsr_utils.py +0 -378
  189. warp/examples/fem/mesh_utils.py +0 -133
  190. warp/examples/fem/plot_utils.py +0 -292
  191. warp_lang-1.2.1.dist-info/RECORD +0 -359
  192. {warp_lang-1.2.1.dist-info → warp_lang-1.3.0.dist-info}/LICENSE.md +0 -0
  193. {warp_lang-1.2.1.dist-info → warp_lang-1.3.0.dist-info}/WHEEL +0 -0
  194. {warp_lang-1.2.1.dist-info → warp_lang-1.3.0.dist-info}/top_level.txt +0 -0
warp/fem/field/field.py CHANGED
@@ -1,9 +1,12 @@
1
- from typing import Any
1
+ from typing import Any, Dict, Optional
2
2
 
3
3
  import warp as wp
4
+ from warp.fem import cache
5
+ from warp.fem.domain import GeometryDomain, Sides
4
6
  from warp.fem.geometry import DeformedGeometry, Geometry
7
+ from warp.fem.operator import integrand
5
8
  from warp.fem.space import FunctionSpace, SpacePartition
6
- from warp.fem.types import Sample
9
+ from warp.fem.types import NULL_ELEMENT_INDEX, ElementKind, Sample
7
10
 
8
11
 
9
12
  class FieldLike:
@@ -36,6 +39,14 @@ class FieldLike:
36
39
  """Value of arguments to be passed to device functions"""
37
40
  raise NotImplementedError
38
41
 
42
+ def gradient_valid(self) -> bool:
43
+ """Whether the gradient operator is implemented for this field."""
44
+ return False
45
+
46
+ def divergence_valid(self) -> bool:
47
+ """Whether the divergence operator is implemented for this field."""
48
+ return False
49
+
39
50
  @staticmethod
40
51
  def eval_inner(args: "ElementEvalArg", s: "Sample"): # noqa: F821
41
52
  """Device function evaluating the inner field value at a sample point"""
@@ -66,14 +77,64 @@ class FieldLike:
66
77
  """Device function evaluating the outer field divergence at a sample point"""
67
78
  raise NotImplementedError
68
79
 
80
+ @staticmethod
81
+ def eval_degree(args: "ElementEvalArg"): # noqa: F821
82
+ """Polynomial degree of the field is applicable, or hint for determination of interpolation order"""
83
+ raise NotImplementedError
84
+
69
85
 
70
- class SpaceField(FieldLike):
86
+ class GeometryField(FieldLike):
87
+ """Base class for fields defined over a geometry"""
88
+
89
+ @property
90
+ def geometry(self) -> Geometry:
91
+ """Geometry over which the field is expressed"""
92
+ raise NotImplementedError
93
+
94
+ @property
95
+ def element_kind(self) -> ElementKind:
96
+ """Kind of element over which the field is expressed"""
97
+ raise NotImplementedError
98
+
99
+ @staticmethod
100
+ def eval_reference_grad_inner(args: "ElementEvalArg", s: "Sample"): # noqa: F821
101
+ """Device function evaluating the inner field gradient with respect to reference element coordinates at a sample point"""
102
+ raise NotImplementedError
103
+
104
+ @staticmethod
105
+ def eval_reference_grad_outer(args: "ElementEvalArg", s: "Sample"): # noqa: F821
106
+ """Device function evaluating the outer field gradient with respect to reference element coordinates at a sample point"""
107
+ raise NotImplementedError
108
+
109
+ def trace(self) -> FieldLike:
110
+ """Trace of this field over lower-dimensional elements"""
111
+ raise NotImplementedError
112
+
113
+ def make_deformed_geometry(self, relative=True) -> Geometry:
114
+ """Returns a deformed version of the underlying geometry, with positions displaced according to this field's values.
115
+
116
+ Args:
117
+ relative: If ``True``, the field is intepreted as a relative displacement over the original geometry. If ``False``, the field values are intepreted as absolute positions.
118
+
119
+ """
120
+ return DeformedGeometry(self, relative=relative)
121
+
122
+
123
+ class SpaceField(GeometryField):
71
124
  """Base class for fields defined over a function space"""
72
125
 
73
126
  def __init__(self, space: FunctionSpace, space_partition: SpacePartition):
74
127
  self._space = space
75
128
  self._space_partition = space_partition
76
129
 
130
+ @property
131
+ def geometry(self) -> Geometry:
132
+ return self._space.geometry
133
+
134
+ @property
135
+ def element_kind(self) -> ElementKind:
136
+ return self._space.element_kind
137
+
77
138
  @property
78
139
  def space(self) -> FunctionSpace:
79
140
  return self._space
@@ -108,7 +169,6 @@ class SpaceField(FieldLike):
108
169
 
109
170
  def _make_eval_degree(self):
110
171
  ORDER = self.space.ORDER
111
- from warp.fem import cache
112
172
 
113
173
  @cache.dynamic_func(suffix=self.name)
114
174
  def degree(args: self.ElementEvalArg):
@@ -130,10 +190,6 @@ class DiscreteField(SpaceField):
130
190
  """Sets degrees of freedom values from an array"""
131
191
  raise NotImplementedError
132
192
 
133
- def trace(self) -> "DiscreteField":
134
- """Trace of this field over a lower-dimensional function space"""
135
- raise NotImplementedError
136
-
137
193
  @staticmethod
138
194
  def set_node_value(args: "FieldLike.EvalArg", node_index: int, value: Any):
139
195
  """Device function setting the value at given node"""
@@ -143,6 +199,393 @@ class DiscreteField(SpaceField):
143
199
  def name(self) -> str:
144
200
  return f"{self.__class__.__qualname__}_{self.space.name}_{self.space_partition.name}"
145
201
 
146
- def make_deformed_geometry(self) -> Geometry:
147
- """Returns a deformed version of the underlying geometry using this field's values as displacement"""
148
- return DeformedGeometry(self)
202
+
203
+ class ImplicitField(GeometryField):
204
+ """Field defined from an arbitrary function over a domain.
205
+ Does not support autodiff yet, so if gradient/divergence evaluation is required corresponding functions must be provided.
206
+
207
+ Args:
208
+ domain: Domain over which the field is defined
209
+ func: Warp function evaluating the field at a given position. Must accept at least one argument, with the first argument being the evaluation position (``wp.vec2`` or ``wp.vec3``).
210
+ values: Optional dictionary of additional argument values to be passed to the evaluation function.
211
+ grad_func: Optional gradient evaluation function; must take same arguments as `func`
212
+ div_func: Optional divergence evaluation function; must take same arguments as `func`
213
+ degree: Optional hint for automatic determination of quadrature orders when integrating this field
214
+ """
215
+
216
+ def __init__(
217
+ self,
218
+ domain: GeometryDomain,
219
+ func: wp.Function,
220
+ values: Optional[Dict[str, Any]] = None,
221
+ grad_func: Optional[wp.Function] = None,
222
+ div_func: Optional[wp.Function] = None,
223
+ degree=0,
224
+ ):
225
+ self.domain = domain
226
+ self._degree = degree
227
+
228
+ if not isinstance(func, wp.Function):
229
+ raise ValueError("Implicit field function must be a warp Function (decorated with `wp.func`)")
230
+
231
+ self._func = func
232
+ self._grad_func = grad_func
233
+ self._div_func = div_func
234
+
235
+ argspec = integrand(func.func).argspec
236
+ arg_types = argspec.annotations
237
+
238
+ pos_arg_type = arg_types.pop(argspec.args[0]) if arg_types else None
239
+ if not pos_arg_type or not wp.types.types_equal(
240
+ pos_arg_type, wp.vec(length=domain.geometry.dimension, dtype=float), match_generic=True
241
+ ):
242
+ raise ValueError(
243
+ f"Implicit field function '{func.func.__name__}' must accept a position as its first argument"
244
+ )
245
+
246
+ self.EvalArg = cache.get_argument_struct(arg_types)
247
+ self.values = values
248
+
249
+ self.ElementEvalArg = self._make_element_eval_arg()
250
+ self.eval_degree = self._make_eval_degree()
251
+
252
+ self.eval_inner = self._make_eval_func(func)
253
+ self.eval_grad_inner = self._make_eval_func(grad_func)
254
+ self.eval_div_inner = self._make_eval_func(div_func)
255
+ self.eval_reference_grad_inner = self._make_eval_reference_grad()
256
+
257
+ self.eval_outer = self.eval_inner
258
+ self.eval_grad_outer = self.eval_grad_inner
259
+ self.eval_div_outer = self.eval_div_inner
260
+ self.eval_reference_grad_outer = self.eval_reference_grad_inner
261
+
262
+ @property
263
+ def values(self):
264
+ return self._func_arg
265
+
266
+ @values.setter
267
+ def values(self, v):
268
+ self._func_arg = cache.populate_argument_struct(self.EvalArg, v, self._func.func.__name__)
269
+
270
+ @property
271
+ def geometry(self) -> Geometry:
272
+ return self.domain.geometry
273
+
274
+ @property
275
+ def element_kind(self) -> ElementKind:
276
+ return self.domain.element_kind
277
+
278
+ def eval_arg_value(self, device):
279
+ return self._func_arg
280
+
281
+ @property
282
+ def degree(self) -> int:
283
+ return self._degree
284
+
285
+ @property
286
+ def name(self) -> str:
287
+ return f"Implicit_{self.domain.name}_{self.degree}_{self.EvalArg.key}"
288
+
289
+ def _make_eval_func(self, func):
290
+ if func is None:
291
+ return None
292
+
293
+ @cache.dynamic_func(
294
+ suffix=f"{self.name}_{func.key}",
295
+ code_transformers=[cache.ExpandStarredArgumentStruct({"args.eval_arg": self.EvalArg})],
296
+ )
297
+ def eval_inner(args: self.ElementEvalArg, s: Sample):
298
+ pos = self.domain.element_position(args.elt_arg, s)
299
+ return func(pos, *args.eval_arg)
300
+
301
+ return eval_inner
302
+
303
+ def _make_eval_reference_grad(self):
304
+ if self.eval_grad_inner is None:
305
+ return None
306
+
307
+ @cache.dynamic_func(suffix=f"{self.eval_grad_inner.key}")
308
+ def eval_reference_grad_inner(args: self.ElementEvalArg, s: Sample):
309
+ return self.eval_grad_inner(args, s) * self.domain.element_deformation_gradient(args.elt_arg, s)
310
+
311
+ return eval_reference_grad_inner
312
+
313
+ def _make_element_eval_arg(self):
314
+ @cache.dynamic_struct(suffix=self.name)
315
+ class ElementEvalArg:
316
+ elt_arg: self.domain.ElementArg
317
+ eval_arg: self.EvalArg
318
+
319
+ return ElementEvalArg
320
+
321
+ def _make_eval_degree(self):
322
+ ORDER = wp.constant(self._degree)
323
+
324
+ @cache.dynamic_func(suffix=self.name)
325
+ def degree(args: self.ElementEvalArg):
326
+ return ORDER
327
+
328
+ return degree
329
+
330
+ def trace(self):
331
+ if self.element_kind == ElementKind.SIDE:
332
+ raise RuntimeError("Trace only available for field defined on cell elements")
333
+
334
+ return ImplicitField(
335
+ domain=Sides(self.domain.geometry_partition),
336
+ func=self._func,
337
+ values={name: getattr(self.values, name) for name in self.EvalArg.vars},
338
+ grad_func=self._grad_func,
339
+ div_func=self._div_func,
340
+ degree=self._degree,
341
+ )
342
+
343
+
344
+ class UniformField(GeometryField):
345
+ """Field defined as a constant value over a domain.
346
+
347
+ Args:
348
+ domain: Domain over which the field is defined
349
+ value: Uniform value over the domain
350
+ """
351
+
352
+ def __init__(self, domain: GeometryDomain, value: Any):
353
+ self.domain = domain
354
+
355
+ if not wp.types.is_value(value):
356
+ raise ValueError("value must be a Warp scalar, vector or matrix")
357
+
358
+ self.dtype = wp.types.type_to_warp(type(value))
359
+ self._value = self.dtype(value)
360
+
361
+ scalar_type = wp.types.type_scalar_type(self.dtype)
362
+ if wp.types.type_is_vector(self.dtype):
363
+ grad_type = wp.mat(shape=(wp.types.type_length(self.dtype), self.geometry.dimension), dtype=scalar_type)
364
+ div_type = scalar_type
365
+ elif wp.types.type_is_matrix(self.dtype):
366
+ grad_type = None
367
+ div_type = wp.vec(length=(wp.types.type_length(self.dtype) // self.geometry.dimension), dtype=scalar_type)
368
+ else:
369
+ div_type = None
370
+ grad_type = wp.vec(length=self.geometry.dimension, dtype=scalar_type)
371
+
372
+ self.EvalArg = self._make_eval_arg()
373
+ self.ElementEvalArg = self._make_element_eval_arg()
374
+ self.eval_degree = self._make_eval_degree()
375
+
376
+ self.eval_inner = self._make_eval_inner()
377
+ self.eval_grad_inner = self._make_eval_zero(grad_type)
378
+ self.eval_div_inner = self._make_eval_zero(div_type)
379
+ self.eval_reference_grad_inner = self.eval_grad_inner
380
+
381
+ self.eval_outer = self.eval_inner
382
+ self.eval_grad_outer = self.eval_grad_inner
383
+ self.eval_div_outer = self.eval_div_inner
384
+ self.eval_reference_grad_outer = self.eval_reference_grad_inner
385
+
386
+ @property
387
+ def value(self):
388
+ return self._value
389
+
390
+ @value.setter
391
+ def value(self, v):
392
+ value_type = wp.types.type_to_warp(type(v))
393
+ assert wp.types.types_equal(value_type, self.dtype)
394
+ self._value = self.dtype(v)
395
+
396
+ @property
397
+ def geometry(self) -> Geometry:
398
+ return self.domain.geometry
399
+
400
+ @property
401
+ def element_kind(self) -> ElementKind:
402
+ return self.domain.element_kind
403
+
404
+ def eval_arg_value(self, device):
405
+ arg = self.EvalArg()
406
+ arg.value = self.value
407
+ return arg
408
+
409
+ @property
410
+ def degree(self) -> int:
411
+ return 0
412
+
413
+ @property
414
+ def name(self) -> str:
415
+ return f"Uniform{self.domain.name}_{wp.types.get_type_code(self.dtype)}"
416
+
417
+ def _make_eval_inner(self):
418
+ @cache.dynamic_func(suffix=self.name)
419
+ def eval_inner(args: self.ElementEvalArg, s: Sample):
420
+ return args.eval_arg.value
421
+
422
+ return eval_inner
423
+
424
+ def _make_eval_zero(self, dtype):
425
+ if dtype is None:
426
+ return None
427
+
428
+ scalar_type = wp.types.type_scalar_type(dtype)
429
+
430
+ @cache.dynamic_func(suffix=f"{self.name}_{wp.types.get_type_code(dtype)}")
431
+ def eval_zero(args: self.ElementEvalArg, s: Sample):
432
+ return dtype(scalar_type(0.0))
433
+
434
+ return eval_zero
435
+
436
+ def _make_eval_arg(self):
437
+ @cache.dynamic_struct(suffix=self.name)
438
+ class EvalArg:
439
+ value: self.dtype
440
+
441
+ return EvalArg
442
+
443
+ def _make_element_eval_arg(self):
444
+ @cache.dynamic_struct(suffix=self.name)
445
+ class ElementEvalArg:
446
+ elt_arg: self.domain.ElementArg
447
+ eval_arg: self.EvalArg
448
+
449
+ return ElementEvalArg
450
+
451
+ def _make_eval_degree(self):
452
+ @cache.dynamic_func(suffix=self.name)
453
+ def degree(args: self.ElementEvalArg):
454
+ return 0
455
+
456
+ return degree
457
+
458
+ def trace(self):
459
+ if self.element_kind == ElementKind.SIDE:
460
+ raise RuntimeError("Trace only available for field defined on cell elements")
461
+
462
+ return UniformField(domain=Sides(self.domain.geometry_partition), value=self.value)
463
+
464
+
465
+ class NonconformingField(GeometryField):
466
+ """Field defined as the map of a DiscreteField over a non-conforming geometry.
467
+
468
+ Args:
469
+ domain: The new domain over which the nonconforming field will be evaluated
470
+ field: Nonconforming discrete field
471
+ background: Uniform value or domain-conforming field determining the value outside of the geometry of definition of `field`
472
+ """
473
+
474
+ _LOOKUP_EPS = wp.constant(1.0e-6)
475
+
476
+ def __init__(self, domain: GeometryDomain, field: DiscreteField, background: Any = 0.0):
477
+ self.domain = domain
478
+
479
+ self.field = field
480
+ self.dtype = field.dtype
481
+
482
+ if not isinstance(background, GeometryField):
483
+ background = UniformField(domain, self.dtype(background))
484
+ elif background.geometry != domain.geometry or background.element_kind != domain.element_kind:
485
+ raise ValueError("Background field must be conforming to the domain")
486
+ self.background = background
487
+
488
+ self.EvalArg = self._make_eval_arg()
489
+ self.ElementEvalArg = self._make_element_eval_arg()
490
+ self.eval_degree = self._make_eval_degree()
491
+
492
+ self.eval_inner = self._make_nonconforming_eval("eval_inner")
493
+ self.eval_grad_inner = self._make_nonconforming_eval("eval_grad_inner")
494
+ self.eval_div_inner = self._make_nonconforming_eval("eval_div_inner")
495
+ self.eval_reference_grad_inner = self._make_eval_reference_grad()
496
+
497
+ # Nonconforming evaluation is position based, does not handle discontinuous fields
498
+ self.eval_outer = self.eval_inner
499
+ self.eval_grad_outer = self.eval_grad_inner
500
+ self.eval_div_outer = self.eval_div_inner
501
+ self.eval_reference_grad_outer = self.eval_reference_grad_inner
502
+
503
+ @property
504
+ def geometry(self) -> Geometry:
505
+ return self.domain.geometry
506
+
507
+ @property
508
+ def element_kind(self) -> ElementKind:
509
+ return self.domain.element_kind
510
+
511
+ @cache.cached_arg_value
512
+ def eval_arg_value(self, device):
513
+ arg = self.EvalArg()
514
+ arg.field_cell_eval_arg = self.field.ElementEvalArg()
515
+ arg.field_cell_eval_arg.elt_arg = self.field.geometry.cell_arg_value(device)
516
+ arg.field_cell_eval_arg.eval_arg = self.field.eval_arg_value(device)
517
+ arg.background_arg = self.background.eval_arg_value(device)
518
+ return arg
519
+
520
+ @property
521
+ def degree(self) -> int:
522
+ return self.field.degree
523
+
524
+ @property
525
+ def name(self) -> str:
526
+ return f"{self.domain.name}_{self.field.name}_{self.background.name}"
527
+
528
+ def _make_nonconforming_eval(self, eval_func_name):
529
+ field_eval = getattr(self.field, eval_func_name)
530
+ bg_eval = getattr(self.background, eval_func_name)
531
+
532
+ if field_eval is None or bg_eval is None:
533
+ return None
534
+
535
+ @cache.dynamic_func(suffix=f"{eval_func_name}_{self.name}")
536
+ def eval_nc(args: self.ElementEvalArg, s: Sample):
537
+ pos = self.domain.element_position(args.elt_arg, s)
538
+ cell_arg = args.eval_arg.field_cell_eval_arg.elt_arg
539
+ nonconforming_s = self.field.geometry.cell_lookup(cell_arg, pos)
540
+ if (
541
+ nonconforming_s.element_index == NULL_ELEMENT_INDEX
542
+ or wp.length_sq(pos - self.field.geometry.cell_position(cell_arg, nonconforming_s))
543
+ > NonconformingField._LOOKUP_EPS
544
+ ):
545
+ return bg_eval(self.background.ElementEvalArg(args.elt_arg, args.eval_arg.background_arg), s)
546
+ return field_eval(
547
+ self.field.ElementEvalArg(cell_arg, args.eval_arg.field_cell_eval_arg.eval_arg), nonconforming_s
548
+ )
549
+
550
+ return eval_nc
551
+
552
+ def _make_eval_reference_grad(self):
553
+ if self.eval_grad_inner is None:
554
+ return None
555
+
556
+ @cache.dynamic_func(suffix=f"{self.eval_grad_inner.key}")
557
+ def eval_reference_grad_inner(args: self.ElementEvalArg, s: Sample):
558
+ return self.eval_grad_inner(args, s) * self.domain.element_deformation_gradient(args.elt_arg, s)
559
+
560
+ return eval_reference_grad_inner
561
+
562
+ def _make_eval_arg(self):
563
+ @cache.dynamic_struct(suffix=self.name)
564
+ class EvalArg:
565
+ field_cell_eval_arg: self.field.ElementEvalArg
566
+ background_arg: self.background.EvalArg
567
+
568
+ return EvalArg
569
+
570
+ def _make_element_eval_arg(self):
571
+ @cache.dynamic_struct(suffix=self.name)
572
+ class ElementEvalArg:
573
+ elt_arg: self.domain.ElementArg
574
+ eval_arg: self.EvalArg
575
+
576
+ return ElementEvalArg
577
+
578
+ def _make_eval_degree(self):
579
+ @cache.dynamic_func(suffix=self.name)
580
+ def degree(args: self.ElementEvalArg):
581
+ return self.field.eval_degree(args.eval_arg.field_cell_eval_arg)
582
+
583
+ return degree
584
+
585
+ def trace(self):
586
+ if self.element_kind == ElementKind.SIDE:
587
+ raise RuntimeError("Trace only available for field defined on cell elements")
588
+
589
+ return NonconformingField(
590
+ domain=Sides(self.domain.geometry_partition), field=self.field, background=self.background.trace()
591
+ )
@@ -28,6 +28,7 @@ class NodalFieldBase(DiscreteField):
28
28
  self.eval_div_outer = self._make_eval_div_outer()
29
29
 
30
30
  self.set_node_value = self._make_set_node_value()
31
+ self.node_partition_index = self._make_node_partition_index()
31
32
 
32
33
  def _make_eval_arg(self):
33
34
  @cache.dynamic_struct(suffix=self.name)
@@ -62,14 +63,15 @@ class NodalFieldBase(DiscreteField):
62
63
  return read_node_value
63
64
 
64
65
  def _make_eval_inner(self):
65
- NODES_PER_ELEMENT = self.space.topology.NODES_PER_ELEMENT
66
-
67
66
  @cache.dynamic_func(suffix=self.name)
68
67
  def eval_inner(args: self.ElementEvalArg, s: Sample):
69
68
  res = self.space.element_inner_weight(
70
69
  args.elt_arg, args.eval_arg.space_arg, s.element_index, s.element_coords, 0
71
70
  ) * self._read_node_value(args, s.element_index, 0)
72
- for k in range(1, NODES_PER_ELEMENT):
71
+ node_count = self.space.topology.element_node_count(
72
+ args.elt_arg, args.eval_arg.topology_arg, s.element_index
73
+ )
74
+ for k in range(1, node_count):
73
75
  res += self.space.element_inner_weight(
74
76
  args.elt_arg, args.eval_arg.space_arg, s.element_index, s.element_coords, k
75
77
  ) * self._read_node_value(args, s.element_index, k)
@@ -78,8 +80,6 @@ class NodalFieldBase(DiscreteField):
78
80
  return eval_inner
79
81
 
80
82
  def _make_eval_grad_inner(self, world_space: bool):
81
- NODES_PER_ELEMENT = self.space.topology.NODES_PER_ELEMENT
82
-
83
83
  if not self.gradient_valid():
84
84
  return None
85
85
 
@@ -91,7 +91,10 @@ class NodalFieldBase(DiscreteField):
91
91
  args.elt_arg, args.eval_arg.space_arg, s.element_index, s.element_coords, 0
92
92
  ),
93
93
  )
94
- for k in range(1, NODES_PER_ELEMENT):
94
+ node_count = self.space.topology.element_node_count(
95
+ args.elt_arg, args.eval_arg.topology_arg, s.element_index
96
+ )
97
+ for k in range(1, node_count):
95
98
  res += utils.generalized_outer(
96
99
  self._read_node_value(args, s.element_index, k),
97
100
  self.space.element_inner_weight_gradient(
@@ -109,8 +112,6 @@ class NodalFieldBase(DiscreteField):
109
112
  return eval_grad_inner_world_space if world_space else eval_grad_inner_ref_space
110
113
 
111
114
  def _make_eval_div_inner(self):
112
- NODES_PER_ELEMENT = self.space.topology.NODES_PER_ELEMENT
113
-
114
115
  if not self.divergence_valid():
115
116
  return None
116
117
 
@@ -128,7 +129,10 @@ class NodalFieldBase(DiscreteField):
128
129
  ),
129
130
  )
130
131
 
131
- for k in range(1, NODES_PER_ELEMENT):
132
+ node_count = self.space.topology.element_node_count(
133
+ args.elt_arg, args.eval_arg.topology_arg, s.element_index
134
+ )
135
+ for k in range(1, node_count):
132
136
  res += utils.generalized_inner(
133
137
  self._read_node_value(args, s.element_index, k),
134
138
  utils.apply_right(
@@ -143,8 +147,6 @@ class NodalFieldBase(DiscreteField):
143
147
  return eval_div_inner
144
148
 
145
149
  def _make_eval_outer(self):
146
- NODES_PER_ELEMENT = self.space.topology.NODES_PER_ELEMENT
147
-
148
150
  @cache.dynamic_func(suffix=self.name)
149
151
  def eval_outer(args: self.ElementEvalArg, s: Sample):
150
152
  res = self.space.element_outer_weight(
@@ -154,7 +156,10 @@ class NodalFieldBase(DiscreteField):
154
156
  s.element_coords,
155
157
  0,
156
158
  ) * self._read_node_value(args, s.element_index, 0)
157
- for k in range(1, NODES_PER_ELEMENT):
159
+ node_count = self.space.topology.element_node_count(
160
+ args.elt_arg, args.eval_arg.topology_arg, s.element_index
161
+ )
162
+ for k in range(1, node_count):
158
163
  res += self.space.element_outer_weight(
159
164
  args.elt_arg,
160
165
  args.eval_arg.space_arg,
@@ -167,8 +172,6 @@ class NodalFieldBase(DiscreteField):
167
172
  return eval_outer
168
173
 
169
174
  def _make_eval_grad_outer(self, world_space: bool):
170
- NODES_PER_ELEMENT = self.space.topology.NODES_PER_ELEMENT
171
-
172
175
  if not self.gradient_valid():
173
176
  return None
174
177
 
@@ -180,7 +183,10 @@ class NodalFieldBase(DiscreteField):
180
183
  args.elt_arg, args.eval_arg.space_arg, s.element_index, s.element_coords, 0
181
184
  ),
182
185
  )
183
- for k in range(1, NODES_PER_ELEMENT):
186
+ node_count = self.space.topology.element_node_count(
187
+ args.elt_arg, args.eval_arg.topology_arg, s.element_index
188
+ )
189
+ for k in range(1, node_count):
184
190
  res += utils.generalized_outer(
185
191
  self._read_node_value(args, s.element_index, k),
186
192
  self.space.element_outer_weight_gradient(
@@ -198,8 +204,6 @@ class NodalFieldBase(DiscreteField):
198
204
  return eval_grad_outer_world_space if world_space else eval_grad_outer_ref_space
199
205
 
200
206
  def _make_eval_div_outer(self):
201
- NODES_PER_ELEMENT = self.space.topology.NODES_PER_ELEMENT
202
-
203
207
  if not self.divergence_valid():
204
208
  return None
205
209
 
@@ -216,7 +220,11 @@ class NodalFieldBase(DiscreteField):
216
220
  grad_transform,
217
221
  ),
218
222
  )
219
- for k in range(1, NODES_PER_ELEMENT):
223
+
224
+ node_count = self.space.topology.element_node_count(
225
+ args.elt_arg, args.eval_arg.topology_arg, s.element_index
226
+ )
227
+ for k in range(1, node_count):
220
228
  res += utils.generalized_inner(
221
229
  self._read_node_value(args, s.element_index, k),
222
230
  utils.apply_right(
@@ -237,6 +245,13 @@ class NodalFieldBase(DiscreteField):
237
245
 
238
246
  return set_node_value
239
247
 
248
+ def _make_node_partition_index(self):
249
+ @cache.dynamic_func(suffix=self.name)
250
+ def node_partition_index(args: self.EvalArg, node_index: int):
251
+ return self.space_partition.partition_node_index(args.eval_arg.partition_arg, node_index)
252
+
253
+ return node_partition_index
254
+
240
255
 
241
256
  class NodalField(NodalFieldBase):
242
257
  """A field holding values for all degrees of freedom at each node of the underlying function space partition