warp-lang 1.7.2__py3-none-win_amd64.whl → 1.8.0__py3-none-win_amd64.whl

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

Potentially problematic release.


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

Files changed (181) hide show
  1. warp/__init__.py +3 -1
  2. warp/__init__.pyi +3489 -1
  3. warp/autograd.py +45 -122
  4. warp/bin/warp-clang.dll +0 -0
  5. warp/bin/warp.dll +0 -0
  6. warp/build.py +241 -252
  7. warp/build_dll.py +125 -26
  8. warp/builtins.py +1907 -384
  9. warp/codegen.py +257 -101
  10. warp/config.py +12 -1
  11. warp/constants.py +1 -1
  12. warp/context.py +657 -223
  13. warp/dlpack.py +1 -1
  14. warp/examples/benchmarks/benchmark_cloth.py +2 -2
  15. warp/examples/benchmarks/benchmark_tile_sort.py +155 -0
  16. warp/examples/core/example_sample_mesh.py +1 -1
  17. warp/examples/core/example_spin_lock.py +93 -0
  18. warp/examples/core/example_work_queue.py +118 -0
  19. warp/examples/fem/example_adaptive_grid.py +5 -5
  20. warp/examples/fem/example_apic_fluid.py +1 -1
  21. warp/examples/fem/example_burgers.py +1 -1
  22. warp/examples/fem/example_convection_diffusion.py +9 -6
  23. warp/examples/fem/example_darcy_ls_optimization.py +489 -0
  24. warp/examples/fem/example_deformed_geometry.py +1 -1
  25. warp/examples/fem/example_diffusion.py +2 -2
  26. warp/examples/fem/example_diffusion_3d.py +1 -1
  27. warp/examples/fem/example_distortion_energy.py +1 -1
  28. warp/examples/fem/example_elastic_shape_optimization.py +387 -0
  29. warp/examples/fem/example_magnetostatics.py +5 -3
  30. warp/examples/fem/example_mixed_elasticity.py +5 -3
  31. warp/examples/fem/example_navier_stokes.py +11 -9
  32. warp/examples/fem/example_nonconforming_contact.py +5 -3
  33. warp/examples/fem/example_streamlines.py +8 -3
  34. warp/examples/fem/utils.py +9 -8
  35. warp/examples/interop/example_jax_ffi_callback.py +2 -2
  36. warp/examples/optim/example_drone.py +1 -1
  37. warp/examples/sim/example_cloth.py +1 -1
  38. warp/examples/sim/example_cloth_self_contact.py +48 -54
  39. warp/examples/tile/example_tile_block_cholesky.py +502 -0
  40. warp/examples/tile/example_tile_cholesky.py +2 -1
  41. warp/examples/tile/example_tile_convolution.py +1 -1
  42. warp/examples/tile/example_tile_filtering.py +1 -1
  43. warp/examples/tile/example_tile_matmul.py +1 -1
  44. warp/examples/tile/example_tile_mlp.py +2 -0
  45. warp/fabric.py +7 -7
  46. warp/fem/__init__.py +5 -0
  47. warp/fem/adaptivity.py +1 -1
  48. warp/fem/cache.py +152 -63
  49. warp/fem/dirichlet.py +2 -2
  50. warp/fem/domain.py +136 -6
  51. warp/fem/field/field.py +141 -99
  52. warp/fem/field/nodal_field.py +85 -39
  53. warp/fem/field/virtual.py +97 -52
  54. warp/fem/geometry/adaptive_nanogrid.py +91 -86
  55. warp/fem/geometry/closest_point.py +13 -0
  56. warp/fem/geometry/deformed_geometry.py +102 -40
  57. warp/fem/geometry/element.py +56 -2
  58. warp/fem/geometry/geometry.py +323 -22
  59. warp/fem/geometry/grid_2d.py +157 -62
  60. warp/fem/geometry/grid_3d.py +116 -20
  61. warp/fem/geometry/hexmesh.py +86 -20
  62. warp/fem/geometry/nanogrid.py +166 -86
  63. warp/fem/geometry/partition.py +59 -25
  64. warp/fem/geometry/quadmesh.py +86 -135
  65. warp/fem/geometry/tetmesh.py +47 -119
  66. warp/fem/geometry/trimesh.py +77 -270
  67. warp/fem/integrate.py +107 -52
  68. warp/fem/linalg.py +25 -58
  69. warp/fem/operator.py +124 -27
  70. warp/fem/quadrature/pic_quadrature.py +36 -14
  71. warp/fem/quadrature/quadrature.py +40 -16
  72. warp/fem/space/__init__.py +1 -1
  73. warp/fem/space/basis_function_space.py +66 -46
  74. warp/fem/space/basis_space.py +17 -4
  75. warp/fem/space/dof_mapper.py +1 -1
  76. warp/fem/space/function_space.py +2 -2
  77. warp/fem/space/grid_2d_function_space.py +4 -1
  78. warp/fem/space/hexmesh_function_space.py +4 -2
  79. warp/fem/space/nanogrid_function_space.py +3 -1
  80. warp/fem/space/partition.py +11 -2
  81. warp/fem/space/quadmesh_function_space.py +4 -1
  82. warp/fem/space/restriction.py +5 -2
  83. warp/fem/space/shape/__init__.py +10 -8
  84. warp/fem/space/tetmesh_function_space.py +4 -1
  85. warp/fem/space/topology.py +52 -21
  86. warp/fem/space/trimesh_function_space.py +4 -1
  87. warp/fem/utils.py +53 -8
  88. warp/jax.py +1 -2
  89. warp/jax_experimental/ffi.py +12 -17
  90. warp/jax_experimental/xla_ffi.py +37 -24
  91. warp/math.py +171 -1
  92. warp/native/array.h +99 -0
  93. warp/native/builtin.h +174 -31
  94. warp/native/coloring.cpp +1 -1
  95. warp/native/exports.h +118 -63
  96. warp/native/intersect.h +3 -3
  97. warp/native/mat.h +5 -10
  98. warp/native/mathdx.cpp +11 -5
  99. warp/native/matnn.h +1 -123
  100. warp/native/quat.h +28 -4
  101. warp/native/sparse.cpp +121 -258
  102. warp/native/sparse.cu +181 -274
  103. warp/native/spatial.h +305 -17
  104. warp/native/tile.h +583 -72
  105. warp/native/tile_radix_sort.h +1108 -0
  106. warp/native/tile_reduce.h +237 -2
  107. warp/native/tile_scan.h +240 -0
  108. warp/native/tuple.h +189 -0
  109. warp/native/vec.h +6 -16
  110. warp/native/warp.cpp +36 -4
  111. warp/native/warp.cu +574 -51
  112. warp/native/warp.h +47 -74
  113. warp/optim/linear.py +5 -1
  114. warp/paddle.py +7 -8
  115. warp/py.typed +0 -0
  116. warp/render/render_opengl.py +58 -29
  117. warp/render/render_usd.py +124 -61
  118. warp/sim/__init__.py +9 -0
  119. warp/sim/collide.py +252 -78
  120. warp/sim/graph_coloring.py +8 -1
  121. warp/sim/import_mjcf.py +4 -3
  122. warp/sim/import_usd.py +11 -7
  123. warp/sim/integrator.py +5 -2
  124. warp/sim/integrator_euler.py +1 -1
  125. warp/sim/integrator_featherstone.py +1 -1
  126. warp/sim/integrator_vbd.py +751 -320
  127. warp/sim/integrator_xpbd.py +1 -1
  128. warp/sim/model.py +265 -260
  129. warp/sim/utils.py +10 -7
  130. warp/sparse.py +303 -166
  131. warp/tape.py +52 -51
  132. warp/tests/cuda/test_conditional_captures.py +1046 -0
  133. warp/tests/cuda/test_streams.py +1 -1
  134. warp/tests/geometry/test_volume.py +2 -2
  135. warp/tests/interop/test_dlpack.py +9 -9
  136. warp/tests/interop/test_jax.py +0 -1
  137. warp/tests/run_coverage_serial.py +1 -1
  138. warp/tests/sim/disabled_kinematics.py +2 -2
  139. warp/tests/sim/{test_vbd.py → test_cloth.py} +296 -113
  140. warp/tests/sim/test_collision.py +159 -51
  141. warp/tests/sim/test_coloring.py +15 -1
  142. warp/tests/test_array.py +254 -2
  143. warp/tests/test_array_reduce.py +2 -2
  144. warp/tests/test_atomic_cas.py +299 -0
  145. warp/tests/test_codegen.py +142 -19
  146. warp/tests/test_conditional.py +47 -1
  147. warp/tests/test_ctypes.py +0 -20
  148. warp/tests/test_devices.py +8 -0
  149. warp/tests/test_fabricarray.py +4 -2
  150. warp/tests/test_fem.py +58 -25
  151. warp/tests/test_func.py +42 -1
  152. warp/tests/test_grad.py +1 -1
  153. warp/tests/test_lerp.py +1 -3
  154. warp/tests/test_map.py +481 -0
  155. warp/tests/test_mat.py +1 -24
  156. warp/tests/test_quat.py +6 -15
  157. warp/tests/test_rounding.py +10 -38
  158. warp/tests/test_runlength_encode.py +7 -7
  159. warp/tests/test_smoothstep.py +1 -1
  160. warp/tests/test_sparse.py +51 -2
  161. warp/tests/test_spatial.py +507 -1
  162. warp/tests/test_struct.py +2 -2
  163. warp/tests/test_tuple.py +265 -0
  164. warp/tests/test_types.py +2 -2
  165. warp/tests/test_utils.py +24 -18
  166. warp/tests/tile/test_tile.py +420 -1
  167. warp/tests/tile/test_tile_mathdx.py +518 -14
  168. warp/tests/tile/test_tile_reduce.py +213 -0
  169. warp/tests/tile/test_tile_shared_memory.py +130 -1
  170. warp/tests/tile/test_tile_sort.py +117 -0
  171. warp/tests/unittest_suites.py +4 -6
  172. warp/types.py +462 -308
  173. warp/utils.py +647 -86
  174. {warp_lang-1.7.2.dist-info → warp_lang-1.8.0.dist-info}/METADATA +20 -6
  175. {warp_lang-1.7.2.dist-info → warp_lang-1.8.0.dist-info}/RECORD +178 -166
  176. warp/stubs.py +0 -3381
  177. warp/tests/sim/test_xpbd.py +0 -399
  178. warp/tests/test_mlp.py +0 -282
  179. {warp_lang-1.7.2.dist-info → warp_lang-1.8.0.dist-info}/WHEEL +0 -0
  180. {warp_lang-1.7.2.dist-info → warp_lang-1.8.0.dist-info}/licenses/LICENSE.md +0 -0
  181. {warp_lang-1.7.2.dist-info → warp_lang-1.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,387 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ ###########################################################################
17
+ # Example: Shape Optimization of a 2D Elastic Cantilever Beam
18
+ #
19
+ # This example demonstrates shape optimization of a 2D elastic cantilever beam using
20
+ # finite element analysis and gradient-based optimization.
21
+ #
22
+ # Problem Setup:
23
+ # - The computational domain is a 2D beam (rectangular mesh), fixed on the left edge
24
+ # (Dirichlet boundary condition: zero displacement).
25
+ # - A constant external load is applied to the right edge of the beam, causing it to deform.
26
+ # - The beam is discretized using finite elements, and the displacement field is solved
27
+ # for the current geometry at each optimization step using a linear elasticity formulation.
28
+ #
29
+ # Shape Optimization Strategy:
30
+ # - The goal is to optimize the shape of the beam to minimize the total squared norm of the
31
+ # stress field (e.g., compliance or strain energy) over the domain.
32
+ # - The positions of the left and right boundary vertices are fixed throughout the optimization
33
+ # to maintain the beam's support and loading conditions.
34
+ # - A volume constraint is enforced to preserve the total material volume, preventing trivial
35
+ # solutions that simply shrink the structure.
36
+ # - An ad-hoc "quality" term is included in the loss function to penalize degenerate or inverted elements,
37
+ # helping to maintain mesh quality (as remeshing would be out of scope for this example).
38
+ # - The optimization is performed by computing the gradient of the objective with respect to
39
+ # the nodal positions (shape derivatives) using the adjoint method or automatic differentiation.
40
+ # - At each iteration, the nodal positions are updated in the direction of decreasing objective,
41
+ # subject to the volume constraint and boundary conditions.
42
+ ###########################################################################
43
+
44
+ import numpy as np
45
+
46
+ import warp as wp
47
+ import warp.examples.fem.utils as fem_example_utils
48
+ import warp.fem as fem
49
+ from warp.optim import Adam
50
+
51
+
52
+ @fem.integrand(kernel_options={"max_unroll": 1})
53
+ def boundary_projector_form(
54
+ s: fem.Sample,
55
+ domain: fem.Domain,
56
+ u: fem.Field,
57
+ v: fem.Field,
58
+ ):
59
+ return wp.dot(u(s), v(s))
60
+
61
+
62
+ @fem.integrand
63
+ def classify_boundary_sides(
64
+ s: fem.Sample,
65
+ domain: fem.Domain,
66
+ left: wp.array(dtype=int),
67
+ right: wp.array(dtype=int),
68
+ ):
69
+ nor = fem.normal(domain, s)
70
+
71
+ if nor[0] < -0.5:
72
+ left[s.qp_index] = 1
73
+ elif nor[0] > 0.5:
74
+ right[s.qp_index] = 1
75
+
76
+
77
+ @wp.func
78
+ def hooke_stress(strain: wp.mat22, lame: wp.vec2):
79
+ """Hookean elasticity"""
80
+ return 2.0 * lame[1] * strain + lame[0] * wp.trace(strain) * wp.identity(n=2, dtype=float)
81
+
82
+
83
+ @fem.integrand
84
+ def stress_field(s: fem.Sample, u: fem.Field, lame: wp.vec2):
85
+ return hooke_stress(fem.D(u, s), lame)
86
+
87
+
88
+ @fem.integrand
89
+ def hooke_elasticity_form(s: fem.Sample, u: fem.Field, v: fem.Field, lame: wp.vec2):
90
+ return wp.ddot(fem.D(v, s), stress_field(s, u, lame))
91
+
92
+
93
+ @fem.integrand
94
+ def applied_load_form(s: fem.Sample, domain: fem.Domain, v: fem.Field, load: wp.vec2):
95
+ return wp.dot(v(s), load)
96
+
97
+
98
+ @fem.integrand
99
+ def loss_form(
100
+ s: fem.Sample, domain: fem.Domain, u: fem.Field, lame: wp.vec2, quality_threshold: float, quality_weight: float
101
+ ):
102
+ stress = stress_field(s, u, lame)
103
+ stress_norm_sq = wp.ddot(stress, stress)
104
+
105
+ # As we're not remeshing, add a "quality" term
106
+ # to avoid degenerate and inverted elements
107
+
108
+ F = fem.deformation_gradient(domain, s)
109
+ U, S, V = wp.svd2(F)
110
+
111
+ quality = wp.min(S) / wp.max(S) / quality_threshold
112
+ quality_pen = -wp.log(wp.max(quality, 0.0001)) * wp.min(0.0, quality - 1.0) * wp.min(0.0, quality - 1.0)
113
+
114
+ return stress_norm_sq + quality_pen * quality_weight
115
+
116
+
117
+ @fem.integrand
118
+ def volume_form():
119
+ return 1.0
120
+
121
+
122
+ @wp.kernel
123
+ def add_volume_loss(
124
+ loss: wp.array(dtype=wp.float32), vol: wp.array(dtype=wp.float32), target_vol: wp.float32, weight: wp.float32
125
+ ):
126
+ loss[0] += weight * (vol[0] - target_vol) * (vol[0] - target_vol)
127
+
128
+
129
+ class Example:
130
+ def __init__(
131
+ self,
132
+ quiet=False,
133
+ degree=2,
134
+ resolution=25,
135
+ mesh="tri",
136
+ poisson_ratio=0.5,
137
+ load=(0.0, -1),
138
+ lr=1.0e-3,
139
+ ):
140
+ self._quiet = quiet
141
+
142
+ # Lame coefficients from Young modulus and Poisson ratio
143
+ self._lame = wp.vec2(1.0 / (1.0 + poisson_ratio) * np.array([poisson_ratio / (1.0 - poisson_ratio), 0.5]))
144
+ self._load = load
145
+
146
+ # procedural rectangular domain definition
147
+ bounds_lo = wp.vec2(0.0, 0.8)
148
+ bounds_hi = wp.vec2(1.0, 1.0)
149
+ self._initial_volume = (bounds_hi - bounds_lo)[0] * (bounds_hi - bounds_lo)[1]
150
+
151
+ if mesh == "tri":
152
+ # triangle mesh, optimize vertices directly
153
+ positions, tri_vidx = fem_example_utils.gen_trimesh(
154
+ res=wp.vec2i(resolution, resolution // 5), bounds_lo=bounds_lo, bounds_hi=bounds_hi
155
+ )
156
+ self._geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=positions)
157
+ self._start_geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=wp.clone(positions))
158
+ self._vertex_positions = positions
159
+ elif mesh == "quad":
160
+ # quad mesh, optimize vertices directly
161
+ positions, quad_vidx = fem_example_utils.gen_quadmesh(
162
+ res=wp.vec2i(resolution, resolution // 5), bounds_lo=bounds_lo, bounds_hi=bounds_hi
163
+ )
164
+ self._geo = fem.Quadmesh2D(quad_vertex_indices=quad_vidx, positions=positions)
165
+ self._start_geo = fem.Quadmesh2D(quad_vertex_indices=quad_vidx, positions=wp.clone(positions))
166
+ self._vertex_positions = positions
167
+ else:
168
+ # grid, optimize nodes of deformation field
169
+ self._start_geo = fem.Grid2D(
170
+ wp.vec2i(resolution, resolution // 5), bounds_lo=bounds_lo, bounds_hi=bounds_hi
171
+ )
172
+ vertex_displacement_space = fem.make_polynomial_space(self._start_geo, degree=1, dtype=wp.vec2)
173
+ vertex_position_field = fem.make_discrete_field(space=vertex_displacement_space)
174
+ vertex_position_field.dof_values = vertex_displacement_space.node_positions()
175
+ self._geo = vertex_position_field.make_deformed_geometry(relative=False)
176
+ self._vertex_positions = vertex_position_field.dof_values
177
+
178
+ # make sure positions are differentiable
179
+ self._vertex_positions.requires_grad = True
180
+
181
+ # Store initial node positions (for rendering)
182
+ self._u_space = fem.make_polynomial_space(self._geo, degree=degree, dtype=wp.vec2)
183
+ self._start_node_positions = self._u_space.node_positions()
184
+
185
+ # displacement field, make sure gradient is stored
186
+ self._u_field = fem.make_discrete_field(space=self._u_space)
187
+ self._u_field.dof_values.requires_grad = True
188
+
189
+ # Trial and test functions
190
+ self._u_test = fem.make_test(space=self._u_space)
191
+ self._u_trial = fem.make_trial(space=self._u_space)
192
+
193
+ # Identify left and right sides for boundary conditions
194
+ boundary = fem.BoundarySides(self._geo)
195
+
196
+ left_mask = wp.zeros(shape=boundary.element_count(), dtype=int)
197
+ right_mask = wp.zeros(shape=boundary.element_count(), dtype=int)
198
+
199
+ fem.interpolate(
200
+ classify_boundary_sides,
201
+ quadrature=fem.RegularQuadrature(boundary, order=0),
202
+ values={"left": left_mask, "right": right_mask},
203
+ )
204
+
205
+ self._left = fem.Subdomain(boundary, element_mask=left_mask)
206
+ self._right = fem.Subdomain(boundary, element_mask=right_mask)
207
+
208
+ # Build projectors for the left-side homogeneous Dirichlet condition
209
+ u_left_bd_test = fem.make_test(space=self._u_space, domain=self._left)
210
+ u_left_bd_trial = fem.make_trial(space=self._u_space, domain=self._left)
211
+ u_left_bd_matrix = fem.integrate(
212
+ boundary_projector_form,
213
+ fields={"u": u_left_bd_trial, "v": u_left_bd_test},
214
+ assembly="nodal",
215
+ output_dtype=float,
216
+ )
217
+ fem.normalize_dirichlet_projector(u_left_bd_matrix)
218
+ self._bd_projector = u_left_bd_matrix
219
+
220
+ # Fixed vertices (that the shape optimization should not move)
221
+ # Build projectors for the left and right subdomains and add them together
222
+ vertex_space = fem.make_polynomial_space(self._geo, degree=1, dtype=wp.vec2)
223
+ u_left_vertex_bd_test = fem.make_test(space=vertex_space, domain=self._left)
224
+ u_left_vertex_bd_trial = fem.make_trial(space=vertex_space, domain=self._left)
225
+ u_right_vertex_bd_test = fem.make_test(space=vertex_space, domain=self._right)
226
+ u_right_vertex_bd_trial = fem.make_trial(space=vertex_space, domain=self._right)
227
+ u_fixed_vertex_matrix = fem.integrate(
228
+ boundary_projector_form,
229
+ fields={"u": u_left_vertex_bd_trial, "v": u_left_vertex_bd_test},
230
+ assembly="nodal",
231
+ output_dtype=float,
232
+ ) + fem.integrate(
233
+ boundary_projector_form,
234
+ fields={"u": u_right_vertex_bd_trial, "v": u_right_vertex_bd_test},
235
+ assembly="nodal",
236
+ output_dtype=float,
237
+ )
238
+ fem.normalize_dirichlet_projector(u_fixed_vertex_matrix)
239
+ self._fixed_vertex_projector = u_fixed_vertex_matrix
240
+
241
+ self._u_right_test = fem.make_test(space=self._u_space, domain=self._right)
242
+
243
+ # initialize renderer
244
+ self.renderer = fem_example_utils.Plot()
245
+
246
+ # Initialize Adam optimizer
247
+ # Current implementation assumes scalar arrays, so cast our vec2 arrays to scalars
248
+ self._vertex_positions_scalar = wp.array(self._vertex_positions, dtype=wp.float32).flatten()
249
+ self._vertex_positions_scalar.grad = wp.array(self._vertex_positions.grad, dtype=wp.float32).flatten()
250
+ self.optimizer = Adam([self._vertex_positions_scalar], lr=lr)
251
+
252
+ def step(self):
253
+ # Forward step, record adjoint tape for forces
254
+ u = self._u_field.dof_values
255
+ u.zero_()
256
+
257
+ u_rhs = wp.empty(self._u_space.node_count(), dtype=wp.vec2f, requires_grad=True)
258
+
259
+ tape = wp.Tape()
260
+
261
+ with tape:
262
+ fem.integrate(
263
+ applied_load_form,
264
+ fields={"v": self._u_right_test},
265
+ values={"load": self._load},
266
+ output=u_rhs,
267
+ )
268
+ # the elastic force will be zero at the first iteration,
269
+ # but including it on the tape is necessary to compute the gradient of the force equilibrium
270
+ # using the implicit function theorem
271
+ # Note that this will be evaluated in the backward pass using the updated values for "_u_field"
272
+ fem.integrate(
273
+ hooke_elasticity_form,
274
+ fields={"u": self._u_field, "v": self._u_test},
275
+ values={"lame": -self._lame},
276
+ output=u_rhs,
277
+ add=True,
278
+ )
279
+
280
+ u_matrix = fem.integrate(
281
+ hooke_elasticity_form,
282
+ fields={"u": self._u_trial, "v": self._u_test},
283
+ values={"lame": self._lame},
284
+ output_dtype=float,
285
+ )
286
+ fem.project_linear_system(u_matrix, u_rhs, self._bd_projector, normalize_projector=False)
287
+
288
+ fem_example_utils.bsr_cg(u_matrix, b=u_rhs, x=u, quiet=self._quiet, tol=1e-6, max_iters=1000)
289
+
290
+ # Record adjoint of linear solve
291
+ # (For nonlinear elasticity, this should use the final hessian, as per implicit function theorem)
292
+ def solve_linear_system():
293
+ fem_example_utils.bsr_cg(u_matrix, b=u.grad, x=u_rhs.grad, quiet=self._quiet, tol=1e-6, max_iters=1000)
294
+ u_rhs.grad -= self._bd_projector @ u_rhs.grad
295
+ self._u_field.dof_values.grad.zero_()
296
+
297
+ tape.record_func(solve_linear_system, arrays=(u_rhs, u))
298
+
299
+ # Evaluate residual
300
+ # Integral of squared difference between simulated position and target positions
301
+ loss = wp.empty(shape=1, dtype=wp.float32, requires_grad=True)
302
+ vol = wp.empty(shape=1, dtype=wp.float32, requires_grad=True)
303
+
304
+ with tape:
305
+ fem.integrate(
306
+ loss_form,
307
+ fields={"u": self._u_field},
308
+ values={"lame": self._lame, "quality_threshold": 0.2, "quality_weight": 20.0},
309
+ domain=self._u_test.domain,
310
+ output=loss,
311
+ )
312
+ print(f"Loss: {loss}")
313
+
314
+ # Add penalization term enforcing constant volume
315
+ fem.integrate(
316
+ volume_form,
317
+ domain=self._u_test.domain,
318
+ output=vol,
319
+ )
320
+
321
+ vol_loss_weight = 100.0
322
+ wp.launch(
323
+ add_volume_loss,
324
+ dim=1,
325
+ inputs=(loss, vol, self._initial_volume, vol_loss_weight),
326
+ )
327
+
328
+ # perform backward step
329
+ tape.backward(loss=loss)
330
+
331
+ # enforce fixed vertices
332
+ self._vertex_positions.grad -= self._fixed_vertex_projector @ self._vertex_positions.grad
333
+
334
+ # update positions and reset tape
335
+ self.optimizer.step([self._vertex_positions_scalar.grad])
336
+ tape.zero()
337
+
338
+ def render(self):
339
+ # Render using fields defined on start geometry
340
+ # (renderer assumes geometry remains fixed for timesampled fields)
341
+ u_space = fem.make_polynomial_space(self._start_geo, degree=self._u_space.degree, dtype=wp.vec2)
342
+ u_field = fem.make_discrete_field(space=u_space)
343
+ rest_field = fem.make_discrete_field(space=u_space)
344
+
345
+ geo_displacement = self._u_space.node_positions() - self._start_node_positions
346
+ u_field.dof_values = self._u_field.dof_values + geo_displacement
347
+ rest_field.dof_values = geo_displacement
348
+
349
+ self.renderer.add_field("displacement", u_field)
350
+ self.renderer.add_field("rest", rest_field)
351
+
352
+
353
+ if __name__ == "__main__":
354
+ import argparse
355
+
356
+ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
357
+ parser.add_argument("--device", type=str, default=None, help="Override the default Warp device.")
358
+ parser.add_argument("--resolution", type=int, default=10, help="Grid resolution.")
359
+ parser.add_argument("--degree", type=int, default=1, help="Polynomial degree of shape functions.")
360
+ parser.add_argument("--mesh", choices=("tri", "quad", "grid"), default="tri", help="Mesh type")
361
+ parser.add_argument(
362
+ "--headless",
363
+ action="store_true",
364
+ help="Run in headless mode, suppressing the opening of any graphical windows.",
365
+ )
366
+ parser.add_argument("--lr", type=float, default=1.0e-3, help="Learning rate.")
367
+ parser.add_argument("--num_iters", type=int, default=250, help="Number of iterations.")
368
+
369
+ args = parser.parse_known_args()[0]
370
+
371
+ with wp.ScopedDevice(args.device):
372
+ example = Example(
373
+ quiet=True,
374
+ degree=args.degree,
375
+ resolution=args.resolution,
376
+ mesh=args.mesh,
377
+ poisson_ratio=0.95,
378
+ load=wp.vec2(0.0, -0.1),
379
+ lr=args.lr,
380
+ )
381
+
382
+ for _k in range(args.num_iters):
383
+ example.step()
384
+ example.render()
385
+
386
+ if not args.headless:
387
+ example.renderer.plot(options={"displacement": {"displacement": {}}, "rest": {"displacement": {}}})
@@ -24,9 +24,9 @@
24
24
  # Div. B = 0
25
25
  #
26
26
  # solved over field A such that B = Curl A,
27
- # and Direchlet homogeneous essential boundary conditions
27
+ # and Dirichlet homogeneous essential boundary conditions
28
28
  #
29
- # This example also illustrates using an ImplictField to warp a grid mesh
29
+ # This example also illustrates using an ImplicitField to warp a grid mesh
30
30
  # to a cylindrical domain
31
31
  ###########################################################################
32
32
 
@@ -199,7 +199,9 @@ class Example:
199
199
  boundary = fem.BoundarySides(sim_geo)
200
200
  u_bd = fem.make_trial(space=A_space, domain=boundary)
201
201
  v_bd = fem.make_test(space=A_space, domain=boundary)
202
- dirichlet_bd_proj = fem.integrate(mass_form, fields={"u": u_bd, "v": v_bd}, nodal=True, output_dtype=float)
202
+ dirichlet_bd_proj = fem.integrate(
203
+ mass_form, fields={"u": u_bd, "v": v_bd}, assembly="nodal", output_dtype=float
204
+ )
203
205
  fem.project_linear_system(lhs, rhs, dirichlet_bd_proj)
204
206
 
205
207
  # solve using Conjugate Residual (numerically rhs may not be in image of lhs)
@@ -192,11 +192,11 @@ class Example:
192
192
  vertical_displacement_form,
193
193
  fields={"v": u_bd_test},
194
194
  values={"displacement": self._displacement},
195
- nodal=True,
195
+ assembly="nodal",
196
196
  output_dtype=wp.vec2d,
197
197
  )
198
198
  u_bd_matrix = fem.integrate(
199
- vertical_boundary_projector_form, fields={"u": u_bd_trial, "v": u_bd_test}, nodal=True
199
+ vertical_boundary_projector_form, fields={"u": u_bd_trial, "v": u_bd_test}, assembly="nodal"
200
200
  )
201
201
 
202
202
  # Stress/velocity coupling
@@ -207,7 +207,9 @@ class Example:
207
207
  gradient_matrix = fem.integrate(displacement_gradient_form, fields={"u": u_trial, "tau": tau_test}).transpose()
208
208
 
209
209
  # Compute inverse of the (block-diagonal) tau mass matrix
210
- tau_inv_mass_matrix = fem.integrate(tensor_mass_form, fields={"sig": tau_trial, "tau": tau_test}, nodal=True)
210
+ tau_inv_mass_matrix = fem.integrate(
211
+ tensor_mass_form, fields={"sig": tau_trial, "tau": tau_test}, assembly="nodal"
212
+ )
211
213
  fem_example_utils.invert_diagonal_bsr_matrix(tau_inv_mass_matrix)
212
214
 
213
215
  # Newton iterations (without line-search for simplicity)
@@ -89,20 +89,22 @@ def div_form(
89
89
 
90
90
 
91
91
  class Example:
92
- def __init__(self, quiet=False, degree=2, resolution=25, Re=1000.0, top_velocity=1.0, tri_mesh=False):
92
+ def __init__(self, quiet=False, degree=2, resolution=25, Re=1000.0, top_velocity=1.0, mesh: str = "grid"):
93
93
  self._quiet = quiet
94
94
 
95
- res = resolution
96
95
  self.sim_dt = 1.0 / resolution
97
96
  self.current_frame = 0
98
97
 
99
98
  viscosity = top_velocity / Re
100
99
 
101
- if tri_mesh:
102
- positions, tri_vidx = fem_example_utils.gen_trimesh(res=wp.vec2i(res))
100
+ if mesh == "tri":
101
+ positions, tri_vidx = fem_example_utils.gen_trimesh(res=wp.vec2i(resolution))
103
102
  geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=positions, build_bvh=True)
103
+ elif mesh == "quad":
104
+ positions, quad_vidx = fem_example_utils.gen_quadmesh(res=wp.vec2i(resolution))
105
+ geo = fem.Quadmesh2D(quad_vertex_indices=quad_vidx, positions=positions, build_bvh=True)
104
106
  else:
105
- geo = fem.Grid2D(res=wp.vec2i(res))
107
+ geo = fem.Grid2D(res=wp.vec2i(resolution))
106
108
 
107
109
  domain = fem.Cells(geometry=geo)
108
110
  boundary = fem.BoundarySides(geo)
@@ -130,7 +132,7 @@ class Example:
130
132
  # build projector for velocity left- and right-hand-sides
131
133
  u_bd_test = fem.make_test(space=u_space, domain=boundary)
132
134
  u_bd_trial = fem.make_trial(space=u_space, domain=boundary)
133
- u_bd_projector = fem.integrate(mass_form, fields={"u": u_bd_trial, "v": u_bd_test}, nodal=True)
135
+ u_bd_projector = fem.integrate(mass_form, fields={"u": u_bd_trial, "v": u_bd_test}, assembly="nodal")
134
136
 
135
137
  # Define an implicit field for our boundary condition value and integrate
136
138
  u_bd_field = fem.ImplicitField(
@@ -139,7 +141,7 @@ class Example:
139
141
  u_bd_value = fem.integrate(
140
142
  mass_form,
141
143
  fields={"u": u_bd_field, "v": u_bd_test},
142
- nodal=True,
144
+ assembly="nodal",
143
145
  output_dtype=wp.vec2d,
144
146
  )
145
147
 
@@ -225,7 +227,7 @@ if __name__ == "__main__":
225
227
  help="Horizontal velocity boundary condition at the top of the domain.",
226
228
  )
227
229
  parser.add_argument("--Re", type=float, default=1000.0, help="Reynolds number.")
228
- parser.add_argument("--tri_mesh", action="store_true", help="Use a triangular mesh.")
230
+ parser.add_argument("--mesh", choices=("grid", "tri", "quad"), default="grid", help="Mesh type.")
229
231
  parser.add_argument(
230
232
  "--headless",
231
233
  action="store_true",
@@ -242,7 +244,7 @@ if __name__ == "__main__":
242
244
  resolution=args.resolution,
243
245
  Re=args.Re,
244
246
  top_velocity=args.top_velocity,
245
- tri_mesh=args.tri_mesh,
247
+ mesh=args.mesh,
246
248
  )
247
249
 
248
250
  for k in range(args.num_frames):
@@ -205,7 +205,9 @@ class Example:
205
205
  # (Note: this is constant per body, this could be precomputed)
206
206
  sym_grad_matrix = fem.integrate(symmetric_grad_form, fields={"u": u_trial, "tau": tau_test})
207
207
 
208
- tau_inv_mass_matrix = fem.integrate(tensor_mass_form, fields={"sig": tau_trial, "tau": tau_test}, nodal=True)
208
+ tau_inv_mass_matrix = fem.integrate(
209
+ tensor_mass_form, fields={"sig": tau_trial, "tau": tau_test}, assembly="nodal"
210
+ )
209
211
  fem_example_utils.invert_diagonal_bsr_matrix(tau_inv_mass_matrix)
210
212
 
211
213
  stress_matrix = tau_inv_mass_matrix @ fem.integrate(
@@ -227,13 +229,13 @@ class Example:
227
229
 
228
230
  # Enforce boundary conditions
229
231
  u_bd_matrix = fem.integrate(
230
- bottom_boundary_projector_form, fields={"u": u_bd_trial, "v": u_bd_test}, nodal=True
232
+ bottom_boundary_projector_form, fields={"u": u_bd_trial, "v": u_bd_test}, assembly="nodal"
231
233
  )
232
234
 
233
235
  # read displacement from other body set create bottom boundary Dirichlet BC
234
236
  other_u_field = fem.field.field.NonconformingField(boundary, other_u_field)
235
237
  u_bd_rhs = fem.integrate(
236
- bottom_boundary_projector_form, fields={"u": other_u_field, "v": u_bd_test}, nodal=True
238
+ bottom_boundary_projector_form, fields={"u": other_u_field, "v": u_bd_test}, assembly="nodal"
237
239
  )
238
240
 
239
241
  fem.project_linear_system(stiffness_matrix, u_rhs, u_bd_matrix, u_bd_rhs)
@@ -158,6 +158,11 @@ class Example:
158
158
  res=res,
159
159
  )
160
160
  self._geo = fem.Tetmesh(tet_vtx_indices, pos, build_bvh=True)
161
+ elif mesh == "hex":
162
+ pos, hex_vtx_indices = fem_example_utils.gen_hexmesh(
163
+ res=res,
164
+ )
165
+ self._geo = fem.Hexmesh(hex_vtx_indices, pos, assume_parallelepiped_cells=True, build_bvh=True)
161
166
  elif mesh == "nano":
162
167
  volume = fem_example_utils.gen_volume(
163
168
  res=res,
@@ -280,7 +285,7 @@ class Example:
280
285
  inflow_test = fem.make_test(u_space, domain=self._inflow)
281
286
  inflow_trial = fem.make_trial(u_space, domain=self._inflow)
282
287
  dirichlet_projector = fem.integrate(
283
- noslip_projector_form, fields={"u": inflow_test, "v": inflow_trial}, nodal=True, output_dtype=float
288
+ noslip_projector_form, fields={"u": inflow_test, "v": inflow_trial}, assembly="nodal", output_dtype=float
284
289
  )
285
290
 
286
291
  freeslip_test = fem.make_test(u_space, domain=self._freeslip)
@@ -288,7 +293,7 @@ class Example:
288
293
  dirichlet_projector += fem.integrate(
289
294
  freeslip_projector_form,
290
295
  fields={"u": freeslip_test, "v": freeslip_trial},
291
- nodal=True,
296
+ assembly="nodal",
292
297
  output_dtype=float,
293
298
  )
294
299
  fem.normalize_dirichlet_projector(dirichlet_projector)
@@ -300,7 +305,7 @@ class Example:
300
305
  rho_test = fem.make_test(u_space)
301
306
  rho_trial = fem.make_trial(u_space)
302
307
  inv_mass_matrix = fem.integrate(
303
- mass_form, fields={"u": rho_trial, "v": rho_test}, nodal=True, output_dtype=float
308
+ mass_form, fields={"u": rho_trial, "v": rho_test}, assembly="nodal", output_dtype=float
304
309
  )
305
310
  fem_example_utils.invert_diagonal_bsr_matrix(inv_mass_matrix)
306
311
 
@@ -13,6 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+
16
17
  from typing import Any, Dict, Optional, Tuple
17
18
 
18
19
  import numpy as np
@@ -23,15 +24,15 @@ from warp.optim.linear import LinearOperator, aslinearoperator, preconditioner
23
24
  from warp.sparse import BsrMatrix, bsr_get_diag, bsr_mv, bsr_transposed
24
25
 
25
26
  __all__ = [
27
+ "Plot",
28
+ "SaddleSystem",
29
+ "bsr_cg",
30
+ "bsr_solve_saddle",
26
31
  "gen_hexmesh",
27
32
  "gen_quadmesh",
28
33
  "gen_tetmesh",
29
34
  "gen_trimesh",
30
- "bsr_cg",
31
- "bsr_solve_saddle",
32
- "SaddleSystem",
33
35
  "invert_diagonal_bsr_matrix",
34
- "Plot",
35
36
  ]
36
37
 
37
38
  # matrix inversion routines contain nested loops,
@@ -576,7 +577,7 @@ class Plot:
576
577
  else:
577
578
  self._usd_renderer.render_points(name, points, radius=self.default_point_radius)
578
579
 
579
- def plot(self, options: Dict[str, Any] = None, backend: str = "auto"):
580
+ def plot(self, options: Optional[Dict[str, Any]] = None, backend: str = "auto"):
580
581
  if options is None:
581
582
  options = {}
582
583
 
@@ -611,7 +612,7 @@ class Plot:
611
612
  offsets = np.cumsum(counts)
612
613
  ranges = np.array([offsets - counts, offsets]).T
613
614
  faces = np.concatenate(
614
- [[count] + list(indices[beg:end]) for (count, (beg, end)) in zip(counts, ranges)]
615
+ [[count, *list(indices[beg:end])] for (count, (beg, end)) in zip(counts, ranges)]
615
616
  )
616
617
  ref_geom = pyvista.PolyData(vertices, faces)
617
618
  else:
@@ -859,8 +860,8 @@ class Plot:
859
860
  value_range = field_options.get("clim", None)
860
861
  if value_range is None:
861
862
  value_range = (
862
- min((np.min(_value_or_magnitude(v)) for v in values)),
863
- max((np.max(_value_or_magnitude(v)) for v in values)),
863
+ min(np.min(_value_or_magnitude(v)) for v in values),
864
+ max(np.max(_value_or_magnitude(v)) for v in values),
864
865
  )
865
866
 
866
867
  return value_range
@@ -45,11 +45,11 @@ def example1():
45
45
  # the Python function to call
46
46
  def print_args(inputs, outputs, attrs, ctx):
47
47
  def buffer_to_string(b):
48
- return str(b.dtype) + str(list(b.shape)) + " @%x" % b.data
48
+ return f"{b.dtype}{list(b.shape)} @{b.data:x}"
49
49
 
50
50
  print("Inputs: ", ", ".join([buffer_to_string(b) for b in inputs]))
51
51
  print("Outputs: ", ", ".join([buffer_to_string(b) for b in outputs]))
52
- print("Attributes: ", "".join(["\n %s: %s" % (k, str(v)) for k, v in attrs.items()]))
52
+ print("Attributes: ", "".join([f"\n {k}: {str(v)}" for k, v in attrs.items()])) # noqa: RUF010
53
53
 
54
54
  # register callback
55
55
  register_ffi_callback("print_args", print_args)
@@ -708,7 +708,7 @@ class Example:
708
708
  self.tape.zero()
709
709
 
710
710
  def step(self):
711
- if self.frame % int((self.num_frames / len(self.targets))) == 0:
711
+ if self.frame % int(self.num_frames / len(self.targets)) == 0:
712
712
  if self.verbose:
713
713
  print(f"Choosing new flight target: {self.target_idx + 1}")
714
714
 
@@ -219,7 +219,7 @@ if __name__ == "__main__":
219
219
  example.render()
220
220
 
221
221
  frame_times = example.profiler["step"]
222
- print("\nAverage frame sim time: {:.2f} ms".format(sum(frame_times) / len(frame_times)))
222
+ print(f"\nAverage frame sim time: {sum(frame_times) / len(frame_times):.2f} ms")
223
223
 
224
224
  if example.renderer:
225
225
  example.renderer.save()