warp-lang 1.4.2__py3-none-macosx_10_13_universal2.whl → 1.5.1__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 (165) hide show
  1. warp/__init__.py +4 -0
  2. warp/autograd.py +43 -8
  3. warp/bin/libwarp.dylib +0 -0
  4. warp/build.py +21 -2
  5. warp/build_dll.py +23 -6
  6. warp/builtins.py +1819 -7
  7. warp/codegen.py +197 -61
  8. warp/config.py +2 -2
  9. warp/context.py +379 -107
  10. warp/examples/assets/pixel.jpg +0 -0
  11. warp/examples/benchmarks/benchmark_cloth_paddle.py +86 -0
  12. warp/examples/benchmarks/benchmark_gemm.py +121 -0
  13. warp/examples/benchmarks/benchmark_interop_paddle.py +158 -0
  14. warp/examples/benchmarks/benchmark_tile.py +179 -0
  15. warp/examples/fem/example_adaptive_grid.py +37 -10
  16. warp/examples/fem/example_apic_fluid.py +3 -2
  17. warp/examples/fem/example_convection_diffusion_dg.py +4 -5
  18. warp/examples/fem/example_deformed_geometry.py +1 -1
  19. warp/examples/fem/example_diffusion_3d.py +47 -4
  20. warp/examples/fem/example_distortion_energy.py +220 -0
  21. warp/examples/fem/example_magnetostatics.py +127 -85
  22. warp/examples/fem/example_nonconforming_contact.py +5 -5
  23. warp/examples/fem/example_stokes.py +3 -1
  24. warp/examples/fem/example_streamlines.py +12 -19
  25. warp/examples/fem/utils.py +38 -15
  26. warp/examples/sim/example_cloth.py +4 -25
  27. warp/examples/sim/example_quadruped.py +2 -1
  28. warp/examples/tile/example_tile_convolution.py +58 -0
  29. warp/examples/tile/example_tile_fft.py +47 -0
  30. warp/examples/tile/example_tile_filtering.py +105 -0
  31. warp/examples/tile/example_tile_matmul.py +79 -0
  32. warp/examples/tile/example_tile_mlp.py +375 -0
  33. warp/fem/__init__.py +8 -0
  34. warp/fem/cache.py +16 -12
  35. warp/fem/dirichlet.py +1 -1
  36. warp/fem/domain.py +44 -1
  37. warp/fem/field/__init__.py +1 -2
  38. warp/fem/field/field.py +31 -19
  39. warp/fem/field/nodal_field.py +101 -49
  40. warp/fem/field/virtual.py +794 -0
  41. warp/fem/geometry/__init__.py +2 -2
  42. warp/fem/geometry/deformed_geometry.py +3 -105
  43. warp/fem/geometry/element.py +13 -0
  44. warp/fem/geometry/geometry.py +165 -7
  45. warp/fem/geometry/grid_2d.py +3 -6
  46. warp/fem/geometry/grid_3d.py +31 -28
  47. warp/fem/geometry/hexmesh.py +3 -46
  48. warp/fem/geometry/nanogrid.py +3 -2
  49. warp/fem/geometry/{quadmesh_2d.py → quadmesh.py} +280 -159
  50. warp/fem/geometry/tetmesh.py +2 -43
  51. warp/fem/geometry/{trimesh_2d.py → trimesh.py} +354 -186
  52. warp/fem/integrate.py +683 -261
  53. warp/fem/linalg.py +404 -0
  54. warp/fem/operator.py +101 -18
  55. warp/fem/polynomial.py +5 -5
  56. warp/fem/quadrature/quadrature.py +45 -21
  57. warp/fem/space/__init__.py +45 -11
  58. warp/fem/space/basis_function_space.py +451 -0
  59. warp/fem/space/basis_space.py +58 -11
  60. warp/fem/space/function_space.py +146 -5
  61. warp/fem/space/grid_2d_function_space.py +80 -66
  62. warp/fem/space/grid_3d_function_space.py +113 -68
  63. warp/fem/space/hexmesh_function_space.py +96 -108
  64. warp/fem/space/nanogrid_function_space.py +62 -110
  65. warp/fem/space/quadmesh_function_space.py +208 -0
  66. warp/fem/space/shape/__init__.py +45 -7
  67. warp/fem/space/shape/cube_shape_function.py +328 -54
  68. warp/fem/space/shape/shape_function.py +10 -1
  69. warp/fem/space/shape/square_shape_function.py +328 -60
  70. warp/fem/space/shape/tet_shape_function.py +269 -19
  71. warp/fem/space/shape/triangle_shape_function.py +238 -19
  72. warp/fem/space/tetmesh_function_space.py +69 -37
  73. warp/fem/space/topology.py +38 -0
  74. warp/fem/space/trimesh_function_space.py +179 -0
  75. warp/fem/utils.py +6 -331
  76. warp/jax_experimental.py +3 -1
  77. warp/native/array.h +15 -0
  78. warp/native/builtin.h +66 -26
  79. warp/native/bvh.h +4 -0
  80. warp/native/coloring.cpp +604 -0
  81. warp/native/cuda_util.cpp +68 -51
  82. warp/native/cuda_util.h +2 -1
  83. warp/native/fabric.h +8 -0
  84. warp/native/hashgrid.h +4 -0
  85. warp/native/marching.cu +8 -0
  86. warp/native/mat.h +14 -3
  87. warp/native/mathdx.cpp +59 -0
  88. warp/native/mesh.h +4 -0
  89. warp/native/range.h +13 -1
  90. warp/native/reduce.cpp +9 -1
  91. warp/native/reduce.cu +7 -0
  92. warp/native/runlength_encode.cpp +9 -1
  93. warp/native/runlength_encode.cu +7 -1
  94. warp/native/scan.cpp +8 -0
  95. warp/native/scan.cu +8 -0
  96. warp/native/scan.h +8 -1
  97. warp/native/sparse.cpp +8 -0
  98. warp/native/sparse.cu +8 -0
  99. warp/native/temp_buffer.h +7 -0
  100. warp/native/tile.h +1854 -0
  101. warp/native/tile_gemm.h +341 -0
  102. warp/native/tile_reduce.h +210 -0
  103. warp/native/volume_builder.cu +8 -0
  104. warp/native/volume_builder.h +8 -0
  105. warp/native/warp.cpp +10 -2
  106. warp/native/warp.cu +369 -15
  107. warp/native/warp.h +12 -2
  108. warp/optim/adam.py +39 -4
  109. warp/paddle.py +29 -12
  110. warp/render/render_opengl.py +140 -67
  111. warp/sim/graph_coloring.py +292 -0
  112. warp/sim/import_urdf.py +8 -8
  113. warp/sim/integrator_euler.py +4 -2
  114. warp/sim/integrator_featherstone.py +115 -44
  115. warp/sim/integrator_vbd.py +6 -0
  116. warp/sim/model.py +109 -32
  117. warp/sparse.py +1 -1
  118. warp/stubs.py +569 -4
  119. warp/tape.py +12 -7
  120. warp/tests/assets/pixel.npy +0 -0
  121. warp/tests/aux_test_instancing_gc.py +18 -0
  122. warp/tests/test_array.py +39 -0
  123. warp/tests/test_codegen.py +81 -1
  124. warp/tests/test_codegen_instancing.py +30 -0
  125. warp/tests/test_collision.py +110 -0
  126. warp/tests/test_coloring.py +251 -0
  127. warp/tests/test_context.py +34 -0
  128. warp/tests/test_examples.py +21 -5
  129. warp/tests/test_fem.py +453 -113
  130. warp/tests/test_func.py +34 -4
  131. warp/tests/test_generics.py +52 -0
  132. warp/tests/test_iter.py +68 -0
  133. warp/tests/test_lerp.py +13 -87
  134. warp/tests/test_mat_scalar_ops.py +1 -1
  135. warp/tests/test_matmul.py +6 -9
  136. warp/tests/test_matmul_lite.py +6 -11
  137. warp/tests/test_mesh_query_point.py +1 -1
  138. warp/tests/test_module_hashing.py +23 -0
  139. warp/tests/test_overwrite.py +45 -0
  140. warp/tests/test_paddle.py +27 -87
  141. warp/tests/test_print.py +56 -1
  142. warp/tests/test_smoothstep.py +17 -83
  143. warp/tests/test_spatial.py +1 -1
  144. warp/tests/test_static.py +3 -3
  145. warp/tests/test_tile.py +744 -0
  146. warp/tests/test_tile_mathdx.py +144 -0
  147. warp/tests/test_tile_mlp.py +383 -0
  148. warp/tests/test_tile_reduce.py +374 -0
  149. warp/tests/test_tile_shared_memory.py +190 -0
  150. warp/tests/test_vbd.py +12 -20
  151. warp/tests/test_volume.py +43 -0
  152. warp/tests/unittest_suites.py +19 -2
  153. warp/tests/unittest_utils.py +4 -2
  154. warp/types.py +340 -74
  155. warp/utils.py +23 -3
  156. {warp_lang-1.4.2.dist-info → warp_lang-1.5.1.dist-info}/METADATA +32 -7
  157. {warp_lang-1.4.2.dist-info → warp_lang-1.5.1.dist-info}/RECORD +160 -133
  158. {warp_lang-1.4.2.dist-info → warp_lang-1.5.1.dist-info}/WHEEL +1 -1
  159. warp/fem/field/test.py +0 -180
  160. warp/fem/field/trial.py +0 -183
  161. warp/fem/space/collocated_function_space.py +0 -102
  162. warp/fem/space/quadmesh_2d_function_space.py +0 -261
  163. warp/fem/space/trimesh_2d_function_space.py +0 -153
  164. {warp_lang-1.4.2.dist-info → warp_lang-1.5.1.dist-info}/LICENSE.md +0 -0
  165. {warp_lang-1.4.2.dist-info → warp_lang-1.5.1.dist-info}/top_level.txt +0 -0
@@ -88,7 +88,7 @@ class Example:
88
88
  geo,
89
89
  discontinuous=True,
90
90
  degree=degree,
91
- family=fem.Polynomial.GAUSS_LEGENDRE,
91
+ family=fem.Polynomial.LOBATTO_GAUSS_LEGENDRE,
92
92
  )
93
93
 
94
94
  # Assemble transport, diffusion and inertia matrices
@@ -110,23 +110,22 @@ class Example:
110
110
 
111
111
  side_test = fem.make_test(space=scalar_space, domain=sides)
112
112
  side_trial = fem.make_trial(space=scalar_space, domain=sides)
113
-
114
- matrix_transport += fem.integrate(
113
+ fem.integrate(
115
114
  upwind_transport_form,
116
115
  fields={"phi": side_trial, "psi": side_test},
117
116
  values={"ang_vel": ang_vel},
117
+ output=matrix_transport,
118
+ add=True,
118
119
  )
119
120
 
120
121
  matrix_diffusion = fem.integrate(
121
122
  diffusion_form,
122
123
  fields={"u": trial, "v": self._test},
123
124
  )
124
-
125
125
  matrix_diffusion += fem.integrate(
126
126
  sip_diffusion_form,
127
127
  fields={"phi": side_trial, "psi": side_test},
128
128
  )
129
-
130
129
  self._matrix = matrix_inertia + matrix_transport + viscosity * matrix_diffusion
131
130
 
132
131
  # Initial condition
@@ -28,7 +28,7 @@ def deformation_field_expr(
28
28
  domain: fem.Domain,
29
29
  ):
30
30
  """
31
- Deformation field mapping the unique square to a circular band
31
+ Deformation field mapping the unit square to a circular band
32
32
  """
33
33
  x = domain(s)
34
34
 
@@ -16,6 +16,8 @@
16
16
  # and homogeneous Dirichlet boundary conditions other sides.
17
17
  ###########################################################################
18
18
 
19
+ import numpy as np
20
+
19
21
  import warp as wp
20
22
  import warp.examples.fem.utils as fem_example_utils
21
23
  import warp.fem as fem
@@ -24,7 +26,7 @@ from warp.sparse import bsr_axpy
24
26
 
25
27
 
26
28
  @fem.integrand
27
- def vert_boundary_projector_form(
29
+ def vertical_boundary_projector_form(
28
30
  s: fem.Sample,
29
31
  domain: fem.Domain,
30
32
  u: fem.Field,
@@ -36,6 +38,18 @@ def vert_boundary_projector_form(
36
38
  return w * u(s) * v(s)
37
39
 
38
40
 
41
+ @fem.integrand
42
+ def y_boundary_projector_form(
43
+ s: fem.Sample,
44
+ domain: fem.Domain,
45
+ u: fem.Field,
46
+ v: fem.Field,
47
+ ):
48
+ # Constrain Y edges
49
+ tangent = fem.deformation_gradient(domain, s)
50
+ return wp.abs(tangent[1]) * u(s) * v(s)
51
+
52
+
39
53
  class Example:
40
54
  def __init__(
41
55
  self,
@@ -77,6 +91,28 @@ class Example:
77
91
  bounds_hi=bounds_hi,
78
92
  )
79
93
  self._geo = fem.Nanogrid(volume)
94
+ elif mesh == "tri":
95
+ pos, quad_vtx_indices = fem_example_utils.gen_trimesh(
96
+ res=res,
97
+ bounds_lo=bounds_lo,
98
+ bounds_hi=bounds_hi,
99
+ )
100
+ pos = pos.numpy()
101
+ pos_z = np.cos(3.0 * pos[:, 0]) * np.sin(4.0 * pos[:, 1])
102
+ pos = np.hstack((pos, np.expand_dims(pos_z, axis=1)))
103
+ pos = wp.array(pos, dtype=wp.vec3)
104
+ self._geo = fem.Trimesh3D(quad_vtx_indices, pos)
105
+ elif mesh == "quad":
106
+ pos, quad_vtx_indices = fem_example_utils.gen_quadmesh(
107
+ res=res,
108
+ bounds_lo=bounds_lo,
109
+ bounds_hi=bounds_hi,
110
+ )
111
+ pos = pos.numpy()
112
+ pos_z = np.cos(3.0 * pos[:, 0]) * np.sin(4.0 * pos[:, 1])
113
+ pos = np.hstack((pos, np.expand_dims(pos_z, axis=1)))
114
+ pos = wp.array(pos, dtype=wp.vec3)
115
+ self._geo = fem.Quadmesh3D(quad_vtx_indices, pos)
80
116
  else:
81
117
  self._geo = fem.Grid3D(
82
118
  res=res,
@@ -108,7 +144,12 @@ class Example:
108
144
 
109
145
  bd_test = fem.make_test(space=self._scalar_space, domain=boundary)
110
146
  bd_trial = fem.make_trial(space=self._scalar_space, domain=boundary)
111
- bd_matrix = fem.integrate(vert_boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, nodal=True)
147
+
148
+ # Pick boundary conditions depending on whether our geometry is a 3d surface or a volume
149
+ boundary_projector_form = (
150
+ vertical_boundary_projector_form if self._geo.cell_dimension == 3 else y_boundary_projector_form
151
+ )
152
+ bd_matrix = fem.integrate(boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, nodal=True)
112
153
 
113
154
  # Diffusion form
114
155
  trial = fem.make_trial(space=self._scalar_space, domain=domain)
@@ -146,7 +187,9 @@ if __name__ == "__main__":
146
187
  parser.add_argument(
147
188
  "--boundary_compliance", type=float, default=0.0, help="Dirichlet boundary condition compliance."
148
189
  )
149
- parser.add_argument("--mesh", choices=("grid", "tet", "hex", "nano", "anano"), default="grid", help="Mesh type.")
190
+ parser.add_argument(
191
+ "--mesh", choices=("grid", "tet", "hex", "nano", "anano", "tri", "quad"), default="grid", help="Mesh type."
192
+ )
150
193
  parser.add_argument(
151
194
  "--headless",
152
195
  action="store_true",
@@ -171,4 +214,4 @@ if __name__ == "__main__":
171
214
  example.render()
172
215
 
173
216
  if not args.headless:
174
- example.renderer.plot(backend="matplotlib")
217
+ example.renderer.plot()
@@ -0,0 +1,220 @@
1
+ # Copyright (c) 2022 NVIDIA CORPORATION. All rights reserved.
2
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
3
+ # and proprietary rights in and to this software, related documentation
4
+ # and any modifications thereto. Any use, reproduction, disclosure or
5
+ # distribution of this software and related documentation without an express
6
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
7
+
8
+ ###########################################################################
9
+ # Example Distortion Energy
10
+ #
11
+ # This example illustrates using a Newton loop to minimize distortion of a
12
+ # 3D surface (u,v) parameterization under a Symmetric Dirichlet energy,
13
+ #
14
+ # E(F) = 1/2 |F|^2 + |F^{-1}|^2
15
+ #
16
+ # with F := dx/du
17
+ ###########################################################################
18
+
19
+ import numpy as np
20
+
21
+ import warp as wp
22
+ import warp.examples.fem.utils as fem_example_utils
23
+ import warp.fem as fem
24
+
25
+
26
+ @fem.integrand
27
+ def distortion_gradient_form(s: fem.Sample, u_cur: fem.Field, v: fem.Field):
28
+ # Symmetric Dirichlet energy gradient (linear form)
29
+ # E = 1/2 (F:F + F^-T:F^-T)
30
+
31
+ F = fem.grad(u_cur, s)
32
+
33
+ F_inv_sq = wp.inverse(F * wp.transpose(F))
34
+ F_inv = F_inv_sq * F
35
+
36
+ dE_dF = F - F_inv_sq * F_inv
37
+
38
+ return wp.ddot(fem.grad(v, s), dE_dF)
39
+
40
+
41
+ @fem.integrand
42
+ def distortion_hessian_form(s: fem.Sample, u_cur: fem.Field, u: fem.Field, v: fem.Field):
43
+ # Symmetric Dirichlet energy approximate hessian (bilinear form)
44
+
45
+ # F:F term
46
+ H = wp.ddot(fem.grad(v, s), fem.grad(u, s))
47
+
48
+ # F^-T:F^-T term
49
+ F = fem.grad(u_cur, s)
50
+ F_inv_sq = wp.inverse(F * wp.transpose(F))
51
+
52
+ # Gauss--Newton (ignore F^-2 derivative)
53
+ H += wp.ddot(F_inv_sq * fem.grad(v, s), F_inv_sq * F_inv_sq * fem.grad(u, s))
54
+
55
+ return H
56
+
57
+
58
+ @fem.integrand
59
+ def initial_guess(
60
+ s: fem.Sample,
61
+ domain: fem.Domain,
62
+ ):
63
+ # initialization for UV parameter
64
+ x = domain(s)
65
+ return wp.vec2(x[0], x[1])
66
+
67
+
68
+ @fem.integrand
69
+ def boundary_projector_form(
70
+ s: fem.Sample,
71
+ domain: fem.Domain,
72
+ u: fem.Field,
73
+ v: fem.Field,
74
+ ):
75
+ # Fix a single point
76
+ # (underconstrained, solution up to a rotation in UV space)
77
+ w = wp.select(s.qp_index == 0, 0.0, 1.0)
78
+ return w * wp.dot(u(s), v(s))
79
+
80
+
81
+ @fem.integrand
82
+ def checkerboard(s: fem.Sample, domain: fem.Domain, u: fem.Field):
83
+ # checkerboard pattern for parameter visualization
84
+ u_s = u(s)
85
+ return wp.sign(wp.cos(16.0 * u_s[0]) * wp.sin(16.0 * u_s[1]))
86
+
87
+
88
+ class Example:
89
+ def __init__(
90
+ self,
91
+ quiet=False,
92
+ degree=2,
93
+ resolution=25,
94
+ mesh="grid",
95
+ nonconforming_stresses=False,
96
+ ):
97
+ self._quiet = quiet
98
+
99
+ def deform_along_z(positions, z_scale=1.0):
100
+ pos = positions.numpy()
101
+ pos_z = z_scale * np.cos(3.0 * pos[:, 0]) * np.sin(4.0 * pos[:, 1])
102
+ pos = np.hstack((pos, np.expand_dims(pos_z, axis=1)))
103
+ return wp.array(pos, dtype=wp.vec3)
104
+
105
+ # Grid or mesh geometry
106
+ if mesh == "tri":
107
+ positions, tri_vidx = fem_example_utils.gen_trimesh(res=wp.vec2i(resolution))
108
+ self._uv_geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=wp.zeros_like(positions))
109
+
110
+ positions = deform_along_z(positions)
111
+ self._geo = fem.Trimesh3D(tri_vertex_indices=tri_vidx, positions=positions)
112
+ elif mesh == "quad":
113
+ positions, quad_vidx = fem_example_utils.gen_quadmesh(res=wp.vec2i(resolution))
114
+ self._uv_geo = fem.Quadmesh2D(quad_vertex_indices=quad_vidx, positions=wp.zeros_like(positions))
115
+
116
+ positions = deform_along_z(positions)
117
+ self._geo = fem.Quadmesh3D(quad_vertex_indices=quad_vidx, positions=positions)
118
+ else:
119
+ positions, quad_vidx = fem_example_utils.gen_quadmesh(res=wp.vec2i(resolution))
120
+ self._uv_geo = fem.Quadmesh2D(quad_vertex_indices=quad_vidx, positions=wp.zeros_like(positions))
121
+
122
+ undef_positions = deform_along_z(positions, z_scale=0.0)
123
+ flat_geo = fem.Quadmesh3D(quad_vertex_indices=quad_vidx, positions=undef_positions)
124
+
125
+ deformation_field = fem.make_discrete_field(fem.make_polynomial_space(flat_geo, dtype=wp.vec3))
126
+ deformation_field.dof_values = deform_along_z(positions)
127
+
128
+ self._geo = deformation_field.make_deformed_geometry(relative=False)
129
+
130
+ # parameter space
131
+ self._u_space = fem.make_polynomial_space(self._geo, degree=degree, dtype=wp.vec2)
132
+ self._u_field = self._u_space.make_field()
133
+ self._du_field = self._u_space.make_field()
134
+ fem.interpolate(initial_guess, dest=self._u_field)
135
+
136
+ # scalar parameter visualization function
137
+ viz_space = fem.make_polynomial_space(self._geo, degree=3, dtype=float)
138
+ self.viz_field = viz_space.make_field()
139
+ # For visualization of uv in 2D space
140
+ uv_space = fem.make_polynomial_space(self._uv_geo, degree=degree, dtype=wp.vec2)
141
+ self._uv_field = uv_space.make_field()
142
+
143
+ self.renderer = fem_example_utils.Plot()
144
+
145
+ def step(self):
146
+ boundary = fem.BoundarySides(self._geo)
147
+ domain = fem.Cells(geometry=self._geo)
148
+
149
+ # Parameter boundary conditions
150
+ u_bd_test = fem.make_test(space=self._u_space, domain=boundary)
151
+ u_bd_trial = fem.make_trial(space=self._u_space, domain=boundary)
152
+ u_bd_matrix = fem.integrate(
153
+ boundary_projector_form, fields={"u": u_bd_trial, "v": u_bd_test}, nodal=True, output_dtype=float
154
+ )
155
+ fem.normalize_dirichlet_projector(u_bd_matrix)
156
+
157
+ u_test = fem.make_test(space=self._u_space, domain=domain)
158
+ u_trial = fem.make_trial(space=self._u_space, domain=domain)
159
+
160
+ # Newton iterations (without line-search for simplicity)
161
+ for _newton_iteration in range(10):
162
+ u_matrix = fem.integrate(
163
+ distortion_hessian_form, fields={"u_cur": self._u_field, "u": u_trial, "v": u_test}, output_dtype=float
164
+ )
165
+
166
+ u_rhs = fem.integrate(
167
+ distortion_gradient_form, fields={"u_cur": self._u_field, "v": u_test}, output_dtype=wp.vec2
168
+ )
169
+
170
+ fem.project_linear_system(u_matrix, u_rhs, u_bd_matrix, normalize_projector=False)
171
+
172
+ # Solve for uv increment
173
+ du = self._du_field.dof_values
174
+ du.zero_()
175
+ fem_example_utils.bsr_cg(u_matrix, b=u_rhs, x=du, quiet=self._quiet)
176
+
177
+ # Accumulate to UV field
178
+ fem.utils.array_axpy(x=du, y=self._u_field.dof_values, alpha=-1.0, beta=1.0)
179
+
180
+ def render(self):
181
+ # Visualization
182
+ fem.interpolate(checkerboard, fields={"u": self._u_field}, dest=self.viz_field)
183
+
184
+ self._uv_field.dof_values = wp.clone(self._u_field.dof_values)
185
+
186
+ self.renderer.add_field("pattern", self.viz_field)
187
+ self.renderer.add_field("uv", self._uv_field)
188
+
189
+
190
+ if __name__ == "__main__":
191
+ import argparse
192
+
193
+ wp.set_module_options({"enable_backward": False})
194
+
195
+ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
196
+ parser.add_argument("--device", type=str, default=None, help="Override the default Warp device.")
197
+ parser.add_argument("--resolution", type=int, default=25, help="Grid resolution.")
198
+ parser.add_argument("--degree", type=int, default=1, help="Polynomial degree of shape functions.")
199
+ parser.add_argument("--mesh", choices=("tri", "quad", "deformed"), default="tri", help="Mesh type")
200
+ parser.add_argument(
201
+ "--headless",
202
+ action="store_true",
203
+ help="Run in headless mode, suppressing the opening of any graphical windows.",
204
+ )
205
+ parser.add_argument("--quiet", action="store_true", help="Suppresses the printing out of iteration residuals.")
206
+
207
+ args = parser.parse_known_args()[0]
208
+
209
+ with wp.ScopedDevice(args.device):
210
+ example = Example(
211
+ quiet=args.quiet,
212
+ degree=args.degree,
213
+ resolution=args.resolution,
214
+ mesh=args.mesh,
215
+ )
216
+ example.step()
217
+ example.render()
218
+
219
+ if not args.headless:
220
+ example.renderer.plot(options={"uv": {"displacement": {}}})
@@ -8,17 +8,18 @@
8
8
  ###########################################################################
9
9
  # Example Magnetostatics
10
10
  #
11
- # This example demonstrates solving an in-plane magnetostatics problem
12
- # using a curl-curl formulation
11
+ # This example demonstrates solving a 3d magnetostatics problem
12
+ # (a copper coil with radial current around a cylindrical iron core)
13
+ # using a curl-curl formulation and H(curl)-conforming function space
13
14
  #
14
15
  # 1/mu Curl B + j = 0
15
16
  # Div. B = 0
16
17
  #
17
- # solved over field A such that B = Curl A, A = (0, 0, a_z),
18
- # and a_z = 0 on the domain boundary
18
+ # solved over field A such that B = Curl A,
19
+ # and Direchlet homogeneous essential boundary conditions
19
20
  #
20
- # This example also illustrates using an ImplictField to warp a square mesh
21
- # to a circular domain
21
+ # This example also illustrates using an ImplictField to warp a grid mesh
22
+ # to a cylindrical domain
22
23
  ###########################################################################
23
24
 
24
25
  import numpy as np
@@ -27,138 +28,179 @@ import warp as wp
27
28
  import warp.examples.fem.utils as fem_example_utils
28
29
  import warp.fem as fem
29
30
 
30
- # Vacuum and copper magnetic permeabilities
31
- MU_0 = wp.constant(np.pi * 4.0e-7)
32
- MU_C = wp.constant(1.25e-6)
31
+ # Physics constants
32
+ MU_0 = wp.constant(np.pi * 4.0e-7) # Vacuum magnetic permeability
33
+ MU_c = wp.constant(1.25e-6) # Copper magnetic permeability
34
+ MU_i = wp.constant(6.0e-3) # Iron magnetic permeability
33
35
 
34
36
 
35
37
  @wp.func
36
- def square_to_disk(x: wp.vec2):
38
+ def cube_to_cylinder(x: wp.vec3):
37
39
  # mapping from unit square to unit disk
38
- return wp.normalize(x) * wp.max(wp.abs(x))
40
+ pos_xz = wp.vec3(x[0], 0.0, x[2])
41
+ return wp.max(wp.abs(pos_xz)) * wp.normalize(pos_xz) + wp.vec3(0.0, x[1], 0.0)
39
42
 
40
43
 
41
44
  @wp.func
42
- def square_to_disk_grad(x: wp.vec2):
45
+ def cube_to_cylinder_grad(x: wp.vec3):
43
46
  # gradient of mapping from unit square to unit disk
44
- if x == wp.vec2(0.0):
45
- return wp.mat22(0.0)
47
+ pos_xz = wp.vec3(x[0], 0.0, x[2])
48
+ if pos_xz == wp.vec3(0.0):
49
+ grad = wp.mat33(0.0)
50
+ else:
51
+ dir_xz = wp.normalize(pos_xz)
52
+ dir_grad = (wp.identity(n=3, dtype=float) - wp.outer(dir_xz, dir_xz)) / wp.length(pos_xz)
53
+
54
+ abs_xz = wp.abs(pos_xz)
55
+ xinf_grad = wp.select(
56
+ abs_xz[0] > abs_xz[2], wp.vec3(0.0, 0.0, wp.sign(pos_xz[2])), wp.vec(wp.sign(pos_xz[0]), 0.0, 0.0)
57
+ )
58
+ grad = dir_grad * wp.max(abs_xz) + wp.outer(dir_xz, xinf_grad)
46
59
 
47
- d = wp.normalize(x)
48
- d_grad = (wp.identity(n=2, dtype=float) - wp.outer(d, d)) / wp.length(x)
60
+ grad[1, 1] = 1.0
61
+ return grad
49
62
 
50
- ax = wp.abs(x)
51
- xinf = wp.max(ax)
52
63
 
53
- xinf_grad = wp.select(ax[0] > ax[1], wp.vec2(0.0, wp.sign(x[1])), wp.vec(wp.sign(x[0]), 0.0))
64
+ @wp.func
65
+ def permeability_field(
66
+ pos: wp.vec3,
67
+ core_radius: float,
68
+ core_height: float,
69
+ coil_internal_radius: float,
70
+ coil_external_radius: float,
71
+ coil_height: float,
72
+ ):
73
+ x = wp.abs(pos[0])
74
+ y = wp.abs(pos[1])
75
+ z = wp.abs(pos[2])
54
76
 
55
- return d_grad * xinf + wp.outer(d, xinf_grad)
77
+ r = wp.sqrt(x * x + z * z)
56
78
 
79
+ if r <= core_radius:
80
+ return wp.select(y < core_height, MU_0, MU_i)
57
81
 
58
- @wp.func
59
- def permeability_field(pos: wp.vec2, coil_height: float, coil_internal_radius: float, coil_external_radius: float):
60
- # space-varying permeability
82
+ if r >= coil_internal_radius and r <= coil_external_radius:
83
+ return wp.select(y < coil_height, MU_0, MU_c)
61
84
 
62
- x = wp.abs(pos[0])
63
- y = wp.abs(pos[1])
64
- return wp.select(
65
- y < coil_height and x > coil_internal_radius and x < coil_external_radius,
66
- MU_0,
67
- MU_C,
68
- )
85
+ return MU_0
69
86
 
70
87
 
71
88
  @wp.func
72
89
  def current_field(
73
- pos: wp.vec2, coil_height: float, coil_internal_radius: float, coil_external_radius: float, current: float
90
+ pos: wp.vec3,
91
+ current: float,
92
+ coil_internal_radius: float,
93
+ coil_external_radius: float,
94
+ coil_height: float,
74
95
  ):
75
- # space-varying current direction along z axis (0, +1 or -1)
76
- x = wp.abs(pos[0])
96
+ x = pos[0]
77
97
  y = wp.abs(pos[1])
78
- return (
79
- wp.select(
80
- y < coil_height and x > coil_internal_radius and x < coil_external_radius,
81
- 0.0,
82
- wp.sign(pos[0]),
83
- )
84
- * current
85
- )
98
+ z = pos[2]
86
99
 
100
+ r = wp.sqrt(x * x + z * z)
87
101
 
88
- @fem.integrand
89
- def curl_z(u: fem.Field, s: fem.Sample):
90
- # projection of curl((0, 0, u)) over z axis
91
- du = fem.grad(u, s)
92
- return wp.vec2(du[1], -du[0])
102
+ return wp.select(
103
+ y < coil_height and r >= coil_internal_radius and r <= coil_external_radius,
104
+ wp.vec3(0.0),
105
+ wp.vec3(z, 0.0, -x) * current / r,
106
+ )
93
107
 
94
108
 
95
109
  @fem.integrand
96
110
  def curl_curl_form(s: fem.Sample, domain: fem.Domain, u: fem.Field, v: fem.Field, mu: fem.Field):
97
- return wp.dot(curl_z(u, s), curl_z(v, s)) / mu(s)
111
+ return wp.dot(fem.curl(u, s), fem.curl(v, s)) / mu(s)
98
112
 
99
113
 
100
114
  @fem.integrand
101
115
  def mass_form(s: fem.Sample, domain: fem.Domain, v: fem.Field, u: fem.Field):
102
- return u(s) * v(s)
116
+ return wp.dot(u(s), v(s))
117
+
118
+
119
+ @fem.integrand
120
+ def curl_expr(s: fem.Sample, u: fem.Field):
121
+ return fem.curl(u, s)
103
122
 
104
123
 
105
124
  class Example:
106
- def __init__(self, quiet=False, degree=2, resolution=32, domain_radius=2.0, current=1.0e6):
125
+ def __init__(self, quiet=False, mesh: str = "grid", resolution=32, domain_radius=2.0, current=1.0e6):
107
126
  # We mesh the unit disk by first meshing the unit square, then building a deformed geometry
108
127
  # from an implicit mapping field
109
- square_geo = fem.Grid2D(
110
- bounds_lo=wp.vec2(-domain_radius, -domain_radius),
111
- bounds_hi=wp.vec2(domain_radius, domain_radius),
112
- res=wp.vec2i(resolution, resolution),
113
- )
114
128
 
115
- def_field = fem.ImplicitField(domain=fem.Cells(square_geo), func=square_to_disk, grad_func=square_to_disk_grad)
116
- disk_geo = def_field.make_deformed_geometry(relative=False)
129
+ if mesh == "hex":
130
+ positions, hex_vidx = fem_example_utils.gen_hexmesh(
131
+ bounds_lo=wp.vec3(-domain_radius, -domain_radius, -domain_radius),
132
+ bounds_hi=wp.vec3(domain_radius, domain_radius, domain_radius),
133
+ res=wp.vec3i(resolution, resolution, resolution),
134
+ )
135
+ cube_geo = fem.Hexmesh(hex_vertex_indices=hex_vidx, positions=positions)
136
+ elif mesh == "tet":
137
+ positions, tet_vidx = fem_example_utils.gen_tetmesh(
138
+ bounds_lo=wp.vec3(-domain_radius, -domain_radius, -domain_radius),
139
+ bounds_hi=wp.vec3(domain_radius, domain_radius, domain_radius),
140
+ res=wp.vec3i(resolution, resolution, resolution),
141
+ )
142
+ cube_geo = fem.Tetmesh(tet_vertex_indices=tet_vidx, positions=positions)
143
+ elif mesh == "nano":
144
+ vol = fem_example_utils.gen_volume(
145
+ bounds_lo=wp.vec3(-domain_radius, -domain_radius, -domain_radius),
146
+ bounds_hi=wp.vec3(domain_radius, domain_radius, domain_radius),
147
+ res=wp.vec3i(resolution, resolution, resolution),
148
+ )
149
+ cube_geo = fem.Nanogrid(grid=vol)
150
+ else:
151
+ cube_geo = fem.Grid3D(
152
+ bounds_lo=wp.vec3(-domain_radius, -domain_radius, -domain_radius),
153
+ bounds_hi=wp.vec3(domain_radius, domain_radius, domain_radius),
154
+ res=wp.vec3i(resolution, resolution, resolution),
155
+ )
156
+
157
+ def_field = fem.ImplicitField(
158
+ domain=fem.Cells(cube_geo), func=cube_to_cylinder, grad_func=cube_to_cylinder_grad
159
+ )
160
+ sim_geo = def_field.make_deformed_geometry(relative=False)
117
161
 
118
- coil_config = {"coil_height": 1.0, "coil_internal_radius": 0.1, "coil_external_radius": 0.3}
162
+ coil_config = {"coil_height": 0.25, "coil_internal_radius": 0.3, "coil_external_radius": 0.4}
163
+ core_config = {"core_height": 1.0, "core_radius": 0.2}
119
164
 
120
- domain = fem.Cells(disk_geo)
121
- self._permeability_field = fem.ImplicitField(domain, func=permeability_field, values=coil_config)
165
+ domain = fem.Cells(sim_geo)
166
+ self._permeability_field = fem.ImplicitField(
167
+ domain, func=permeability_field, values=dict(**coil_config, **core_config)
168
+ )
122
169
  self._current_field = fem.ImplicitField(domain, func=current_field, values=dict(current=current, **coil_config))
123
170
 
124
- z_space = fem.make_polynomial_space(disk_geo, degree=degree, element_basis=fem.ElementBasis.LAGRANGE)
125
- xy_space = fem.make_polynomial_space(
126
- disk_geo, degree=degree, element_basis=fem.ElementBasis.LAGRANGE, dtype=wp.vec2
171
+ A_space = fem.make_polynomial_space(
172
+ sim_geo, degree=1, element_basis=fem.ElementBasis.NEDELEC_FIRST_KIND, dtype=wp.vec3
127
173
  )
174
+ self.A_field = A_space.make_field()
128
175
 
129
- self.A_field = z_space.make_field()
130
- self.B_field = xy_space.make_field()
176
+ B_space = fem.make_polynomial_space(sim_geo, degree=1, element_basis=fem.ElementBasis.LAGRANGE, dtype=wp.vec3)
177
+ self.B_field = B_space.make_field()
131
178
 
132
179
  self.renderer = fem_example_utils.Plot()
133
180
 
134
181
  def step(self):
135
- z_space = self.A_field.space
136
- disk_geo = z_space.geometry
182
+ A_space = self.A_field.space
183
+ sim_geo = A_space.geometry
137
184
 
138
- u = fem.make_trial(space=z_space)
139
- v = fem.make_test(space=z_space)
140
- lhs = fem.integrate(curl_curl_form, fields={"u": u, "v": v, "mu": self._permeability_field})
141
- rhs = fem.integrate(mass_form, fields={"v": v, "u": self._current_field})
185
+ u = fem.make_trial(space=A_space)
186
+ v = fem.make_test(space=A_space)
187
+ lhs = fem.integrate(curl_curl_form, fields={"u": u, "v": v, "mu": self._permeability_field}, output_dtype=float)
188
+ rhs = fem.integrate(mass_form, fields={"v": v, "u": self._current_field}, output_dtype=float)
142
189
 
143
190
  # Dirichlet BC
144
- boundary = fem.BoundarySides(disk_geo)
145
- u_bd = fem.make_trial(space=z_space, domain=boundary)
146
- v_bd = fem.make_test(space=z_space, domain=boundary)
147
- dirichlet_bd_proj = fem.integrate(mass_form, fields={"u": u_bd, "v": v_bd}, nodal=True)
191
+ boundary = fem.BoundarySides(sim_geo)
192
+ u_bd = fem.make_trial(space=A_space, domain=boundary)
193
+ v_bd = fem.make_test(space=A_space, domain=boundary)
194
+ dirichlet_bd_proj = fem.integrate(mass_form, fields={"u": u_bd, "v": v_bd}, nodal=True, output_dtype=float)
148
195
  fem.project_linear_system(lhs, rhs, dirichlet_bd_proj)
149
196
 
150
- x = wp.zeros_like(rhs)
151
- fem_example_utils.bsr_cg(lhs, b=rhs, x=x, tol=1.0e-8, quiet=False)
152
-
153
- # make sure result is exactly zero outside of circle
154
- wp.sparse.bsr_mv(dirichlet_bd_proj, x=x, y=x, alpha=-1.0, beta=1.0)
155
- wp.utils.array_cast(in_array=x, out_array=self.A_field.dof_values)
197
+ # solve using Conjugate Residual (numerically rhs may not be in image of lhs)
198
+ fem_example_utils.bsr_cg(lhs, b=rhs, x=self.A_field.dof_values, method="cr", max_iters=250, quiet=False)
156
199
 
157
200
  # compute B as curl(A)
158
- fem.interpolate(curl_z, dest=self.B_field, fields={"u": self.A_field})
201
+ fem.interpolate(curl_expr, dest=self.B_field, fields={"u": self.A_field})
159
202
 
160
203
  def render(self):
161
- self.renderer.add_field("A", self.A_field)
162
204
  self.renderer.add_field("B", self.B_field)
163
205
 
164
206
 
@@ -170,7 +212,7 @@ if __name__ == "__main__":
170
212
  parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
171
213
  parser.add_argument("--device", type=str, default=None, help="Override the default Warp device.")
172
214
  parser.add_argument("--resolution", type=int, default=32, help="Grid resolution.")
173
- parser.add_argument("--degree", type=int, default=2, help="Polynomial degree of shape functions.")
215
+ parser.add_argument("--mesh", type=str, default="grid", choices=["tet", "hex", "grid", "nano"], help="Mesh type.")
174
216
  parser.add_argument("--radius", type=float, default=2.0, help="Radius of simulation domain.")
175
217
  parser.add_argument(
176
218
  "--headless",
@@ -182,9 +224,9 @@ if __name__ == "__main__":
182
224
  args = parser.parse_known_args()[0]
183
225
 
184
226
  with wp.ScopedDevice(args.device):
185
- example = Example(quiet=args.quiet, degree=args.degree, resolution=args.resolution, domain_radius=args.radius)
227
+ example = Example(quiet=args.quiet, mesh=args.mesh, resolution=args.resolution, domain_radius=args.radius)
186
228
  example.step()
187
229
  example.render()
188
230
 
189
231
  if not args.headless:
190
- example.renderer.plot({"A": {"contours": {"levels": 30}}, "B": {"streamlines": {"density": 1.0}}})
232
+ example.renderer.plot({"B": {"streamlines": {"density": 1.0}}})
@@ -210,11 +210,11 @@ class Example:
210
210
 
211
211
  # Add boundary stress from other solid field
212
212
  other_stress_field = fem.field.field.NonconformingField(boundary, other_stress_field)
213
- fem.utils.array_axpy(
214
- y=u_rhs,
215
- x=fem.integrate(
216
- boundary_stress_form, fields={"u": u_bd_test, "tau": other_stress_field}, output_dtype=wp.vec2d
217
- ),
213
+ fem.integrate(
214
+ boundary_stress_form,
215
+ fields={"u": u_bd_test, "tau": other_stress_field},
216
+ output=u_rhs,
217
+ add=True,
218
218
  )
219
219
 
220
220
  # Enforce boundary conditions