warp-lang 1.7.2rc1__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.2rc1.dist-info → warp_lang-1.8.0.dist-info}/METADATA +20 -6
  175. {warp_lang-1.7.2rc1.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.2rc1.dist-info → warp_lang-1.8.0.dist-info}/WHEEL +0 -0
  180. {warp_lang-1.7.2rc1.dist-info → warp_lang-1.8.0.dist-info}/licenses/LICENSE.md +0 -0
  181. {warp_lang-1.7.2rc1.dist-info → warp_lang-1.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,489 @@
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 2D Darcy Flow using Level Set Method
18
+ #
19
+ # This example demonstrates the use of a level set-based shape optimization technique
20
+ # to maximize the total Darcy flow (inflow) through a 2D square domain. The domain
21
+ # contains a material region whose shape is implicitly represented by a level set function.
22
+ #
23
+ # Physical Setup:
24
+ # - The computational domain is a unit square, discretized using either a structured grid
25
+ # or a triangular mesh.
26
+ # - The material region within the domain is defined by the zero level set of a scalar field.
27
+ # - The permeability of the domain is a smooth function of the level set, with high permeability
28
+ # inside the material region and low permeability outside.
29
+ # - Boundary conditions are set such that one side of the domain has a prescribed inflow pressure
30
+ # and the opposite side has a prescribed outflow (Dirichlet) pressure, driving Darcy flow
31
+ # through the material region.
32
+ #
33
+ # Optimization Goal:
34
+ # - The objective is to optimize the shape of the material region (by evolving the level set)
35
+ # to maximize the total inflow (Darcy flux) across the inflow boundary, subject to a constraint
36
+ # on the total volume of the material region.
37
+ #
38
+ # Numerical Approach:
39
+ # - The pressure field is solved using the finite element method (FEM) for the current material
40
+ # configuration.
41
+ # - The level set function is updated using the adjoint method: the gradient of the objective
42
+ # with respect to the level set is computed via automatic differentiation, and the level set
43
+ # is advected in the direction of increasing inflow.
44
+ # - The optimization is performed iteratively, with each iteration consisting of a forward
45
+ # pressure solve, loss evaluation, backward (adjoint) computation, and level set update.
46
+ # - The code supports both continuous and discontinuous Galerkin formulations for the level set
47
+ # advection step.
48
+ #
49
+ # Visualization:
50
+ # - The script provides visualization of the velocity field and the evolving material region.
51
+ #
52
+ ###########################################################################
53
+
54
+
55
+ import warp as wp
56
+ import warp.examples.fem.utils as fem_example_utils
57
+ import warp.fem as fem
58
+
59
+
60
+ @fem.integrand
61
+ def classify_boundary_sides(
62
+ s: fem.Sample,
63
+ domain: fem.Domain,
64
+ dirichlet: wp.array(dtype=int),
65
+ inflow: wp.array(dtype=int),
66
+ ):
67
+ """Assign boundary sides to inflow or Dirichlet subdomains based on normal direction"""
68
+ nor = fem.normal(domain, s)
69
+
70
+ if nor[0] < -0.5:
71
+ inflow[s.qp_index] = 1
72
+ dirichlet[s.qp_index] = 1
73
+ elif nor[0] > 0.5:
74
+ dirichlet[s.qp_index] = 1
75
+
76
+
77
+ @wp.func
78
+ def initial_level_set(x: wp.vec2, radius: float):
79
+ """Initial level set function for the material region -- three circles"""
80
+
81
+ return (
82
+ wp.min(
83
+ wp.vec3(
84
+ wp.length(x - wp.vec2(0.667, 0.5)),
85
+ wp.length(x - wp.vec2(0.333, 0.333)),
86
+ wp.length(x - wp.vec2(0.333, 0.667)),
87
+ )
88
+ )
89
+ - radius
90
+ )
91
+
92
+
93
+ @fem.integrand
94
+ def identity_form(
95
+ s: fem.Sample,
96
+ p: fem.Field,
97
+ q: fem.Field,
98
+ ):
99
+ return p(s) * q(s)
100
+
101
+
102
+ @fem.integrand
103
+ def material_fraction(s: fem.Sample, level_set: fem.Field, smoothing: float):
104
+ """Sigmoid approximation of the level set interior"""
105
+ return 1.0 / (1.0 + wp.exp(-level_set(s) / smoothing))
106
+
107
+
108
+ @fem.integrand
109
+ def permeability(s: fem.Sample, level_set: fem.Field, smoothing: float):
110
+ """Define permeability as strictly proportional to material fraction (arbitrary choice)"""
111
+ return material_fraction(s, level_set, smoothing)
112
+
113
+
114
+ @fem.integrand
115
+ def velocity_field(s: fem.Sample, level_set: fem.Field, p: fem.Field, smoothing: float):
116
+ """Velocity field based on permeability and pressure gradient according to Darcy's law"""
117
+ return -permeability(s, level_set, smoothing) * fem.grad(p, s)
118
+
119
+
120
+ @fem.integrand
121
+ def diffusion_form(s: fem.Sample, level_set: fem.Field, p: fem.Field, q: fem.Field, smoothing: float, scale: float):
122
+ """Inhomogeneous diffusion form"""
123
+ return scale * wp.dot(velocity_field(s, level_set, p, smoothing), fem.grad(q, s))
124
+
125
+
126
+ @fem.integrand
127
+ def inflow_velocity(s: fem.Sample, domain: fem.Domain, level_set: fem.Field, p: fem.Field, smoothing: float):
128
+ return wp.dot(velocity_field(s, level_set, p, smoothing), fem.normal(domain, s))
129
+
130
+
131
+ @fem.integrand
132
+ def volume_form(s: fem.Sample, level_set: fem.Field, smoothing: float):
133
+ return material_fraction(s, level_set, smoothing)
134
+
135
+
136
+ @wp.kernel
137
+ def combine_losses(
138
+ loss: wp.array(dtype=wp.float32),
139
+ vol: wp.array(dtype=wp.float32),
140
+ target_vol: wp.float32,
141
+ vol_weight: wp.float32,
142
+ ):
143
+ loss[0] += vol_weight * (vol[0] - target_vol) * (vol[0] - target_vol)
144
+
145
+
146
+ @fem.integrand
147
+ def advected_level_set_semi_lagrangian(
148
+ s: fem.Sample, domain: fem.Domain, level_set: fem.Field, velocity: fem.Field, dt: float
149
+ ):
150
+ x_prev = domain(s) - velocity(s) * dt
151
+ s_prev = fem.lookup(domain, x_prev, guess=s)
152
+ return level_set(s_prev)
153
+
154
+
155
+ # Discontinuous Galerkin variant of level set advection
156
+
157
+
158
+ @fem.integrand
159
+ def level_set_transport_form(s: fem.Sample, level_set: fem.Field, psi: fem.Field, velocity: fem.Field, dt: float):
160
+ return dt * wp.dot(fem.grad(level_set, s), velocity(s)) * psi(s)
161
+
162
+
163
+ @fem.integrand
164
+ def level_set_transport_form_upwind(
165
+ s: fem.Sample, domain: fem.Domain, level_set: fem.Field, psi: fem.Field, velocity: fem.Field, dt: float
166
+ ):
167
+ vel = dt * velocity(s)
168
+ vel_n = wp.dot(vel, fem.normal(domain, s))
169
+ return fem.jump(level_set, s) * (-fem.average(psi, s) * vel_n + 0.5 * fem.jump(psi, s) * wp.abs(vel_n))
170
+
171
+
172
+ @fem.integrand
173
+ def advected_level_set_upwind(
174
+ s: fem.Sample, domain: fem.Domain, level_set: fem.Field, transport_integrals: wp.array(dtype=float)
175
+ ):
176
+ return level_set(s) - transport_integrals[s.qp_index] / (fem.measure(domain, s) * s.qp_weight)
177
+
178
+
179
+ class Example:
180
+ def __init__(
181
+ self, quiet=False, degree=2, resolution=25, mesh: str = "grid", dt: float = 1.0, discontinuous: bool = False
182
+ ):
183
+ self._quiet = quiet
184
+
185
+ self._smoothing = 0.5 / resolution # smoothing for level set interface approximation as sigmoid
186
+ self._dt = dt # level set advection time step (~gradient step size)
187
+ self._discontinuous = discontinuous
188
+
189
+ if mesh == "tri":
190
+ positions, tri_vidx = fem_example_utils.gen_trimesh(res=wp.vec2i(resolution, resolution))
191
+ self._geo = fem.Trimesh2D(tri_vertex_indices=tri_vidx, positions=positions, build_bvh=True)
192
+ else:
193
+ self._geo = fem.Grid2D(res=wp.vec2i(resolution, resolution))
194
+
195
+ # Pressure, level set, and level set velocity spaces
196
+ self._p_space = fem.make_polynomial_space(self._geo, degree=degree, dtype=float)
197
+ self._ls_space = fem.make_polynomial_space(self._geo, degree=degree, dtype=float, discontinuous=discontinuous)
198
+ self._v_space = fem.make_polynomial_space(self._geo, degree=degree, dtype=wp.vec2)
199
+
200
+ # pressure field
201
+ self._p_field = fem.make_discrete_field(space=self._p_space)
202
+ self._p_field.dof_values.requires_grad = True
203
+
204
+ # level set field
205
+ self._level_set_field = fem.make_discrete_field(space=self._ls_space)
206
+ self._level_set_field.dof_values.requires_grad = True
207
+
208
+ # level set advection velocity field
209
+ self._level_set_velocity_field = fem.make_discrete_field(space=self._v_space)
210
+ self._level_set_velocity_field.dof_values.requires_grad = True
211
+
212
+ fem.interpolate(
213
+ fem.ImplicitField(fem.Cells(self._geo), initial_level_set, values={"radius": 0.125}),
214
+ dest=self._level_set_field,
215
+ )
216
+
217
+ # recording initial volume, we want to preserve that when doing optimization
218
+ self._target_vol = fem.integrate(
219
+ volume_form,
220
+ domain=fem.Cells(self._geo),
221
+ fields={"level_set": self._level_set_field},
222
+ values={"smoothing": self._smoothing},
223
+ )
224
+
225
+ # Trial and test functions for pressure solve
226
+ self._p_test = fem.make_test(space=self._p_space)
227
+ self._p_trial = fem.make_trial(space=self._p_space)
228
+
229
+ # For discontinuous level set advection
230
+ if self._discontinuous:
231
+ self._ls_test = fem.make_test(space=self._ls_space)
232
+ self._ls_sides_test = fem.make_test(space=self._ls_space, domain=fem.Sides(self._geo))
233
+
234
+ # Identify inflow and outflow sides
235
+ boundary = fem.BoundarySides(self._geo)
236
+
237
+ inflow_mask = wp.zeros(shape=boundary.element_count(), dtype=int)
238
+ dirichlet_mask = wp.zeros(shape=boundary.element_count(), dtype=int)
239
+
240
+ fem.interpolate(
241
+ classify_boundary_sides,
242
+ quadrature=fem.RegularQuadrature(boundary, order=0),
243
+ values={"inflow": inflow_mask, "dirichlet": dirichlet_mask},
244
+ )
245
+
246
+ self._inflow = fem.Subdomain(boundary, element_mask=inflow_mask)
247
+ self._dirichlet = fem.Subdomain(boundary, element_mask=dirichlet_mask)
248
+
249
+ # Build projector for the inflow and outflow homogeneous Dirichlet condition
250
+ p_dirichlet_bd_test = fem.make_test(space=self._p_space, domain=self._dirichlet)
251
+ p_dirichlet_bd_trial = fem.make_trial(space=self._p_space, domain=self._dirichlet)
252
+ p_dirichlet_bd_matrix = fem.integrate(
253
+ identity_form,
254
+ fields={"p": p_dirichlet_bd_trial, "q": p_dirichlet_bd_test},
255
+ assembly="nodal",
256
+ output_dtype=float,
257
+ )
258
+
259
+ # Inflow prescribed pressure
260
+ p_inflow_bd_test = fem.make_test(space=self._p_space, domain=self._inflow)
261
+ p_inflow_bd_value = fem.integrate(
262
+ identity_form,
263
+ fields={"p": fem.UniformField(self._inflow, 1.0), "q": p_inflow_bd_test},
264
+ assembly="nodal",
265
+ output_dtype=float,
266
+ )
267
+ fem.normalize_dirichlet_projector(p_dirichlet_bd_matrix, p_inflow_bd_value)
268
+
269
+ self._bd_projector = p_dirichlet_bd_matrix
270
+ self._bd_prescribed_value = p_inflow_bd_value
271
+
272
+ self.renderer = fem_example_utils.Plot()
273
+
274
+ def step(self):
275
+ p = self._p_field.dof_values
276
+ p.zero_()
277
+ v = self._level_set_velocity_field.dof_values
278
+ v.zero_()
279
+
280
+ # Advected level set field, used in adjoint computations
281
+ advected_level_set = fem.make_discrete_field(space=self._ls_space)
282
+ advected_level_set.dof_values.assign(self._level_set_field.dof_values)
283
+ advected_level_set.dof_values.requires_grad = True
284
+ advected_level_set_restriction = fem.make_restriction(advected_level_set, domain=self._p_test.domain)
285
+
286
+ # Forward step, record adjoint tape for forces
287
+ p_rhs = wp.empty(self._p_space.node_count(), dtype=wp.float32, requires_grad=True)
288
+
289
+ tape = wp.Tape()
290
+ with tape:
291
+ # Dummy advection step, so backward pass can compute adjoint w.r.t advection velocity
292
+ self.advect_level_set(
293
+ level_set_in=self._level_set_field,
294
+ level_set_out=advected_level_set_restriction,
295
+ velocity=self._level_set_velocity_field,
296
+ dt=1.0,
297
+ )
298
+
299
+ # Left-hand-side of implicit solve (zero if p=0, but required for adjoint computation through implicit function theorem)
300
+ fem.integrate(
301
+ diffusion_form,
302
+ fields={
303
+ "level_set": advected_level_set,
304
+ "p": self._p_field,
305
+ "q": self._p_test,
306
+ },
307
+ values={"smoothing": self._smoothing, "scale": -1.0},
308
+ output=p_rhs,
309
+ )
310
+
311
+ # Diffusion matrix (inhomogeneous Poisson)
312
+ p_matrix = fem.integrate(
313
+ diffusion_form,
314
+ fields={
315
+ "level_set": advected_level_set,
316
+ "p": self._p_trial,
317
+ "q": self._p_test,
318
+ },
319
+ values={"smoothing": self._smoothing, "scale": 1.0},
320
+ output_dtype=float,
321
+ )
322
+
323
+ # Project to enforce Dirichlet boundary conditions then solve linear system
324
+ fem.project_linear_system(
325
+ p_matrix, p_rhs, self._bd_projector, self._bd_prescribed_value, normalize_projector=False
326
+ )
327
+
328
+ fem_example_utils.bsr_cg(p_matrix, b=p_rhs, x=p, quiet=self._quiet, tol=1e-6, max_iters=1000)
329
+
330
+ # Record adjoint of linear solve
331
+ def solve_linear_system():
332
+ fem_example_utils.bsr_cg(p_matrix, b=p.grad, x=p_rhs.grad, quiet=self._quiet, tol=1e-6, max_iters=1000)
333
+ p_rhs.grad -= self._bd_projector @ p_rhs.grad
334
+
335
+ tape.record_func(solve_linear_system, arrays=(p_rhs, p))
336
+
337
+ # Evaluate losses
338
+ loss = wp.empty(shape=1, dtype=wp.float32, requires_grad=True)
339
+ vol = wp.empty(shape=1, dtype=wp.float32, requires_grad=True)
340
+
341
+ with tape:
342
+ # Main objective: inflow flux
343
+ fem.integrate(
344
+ inflow_velocity,
345
+ fields={"level_set": advected_level_set.trace(), "p": self._p_field.trace()},
346
+ values={"smoothing": self._smoothing},
347
+ domain=self._inflow,
348
+ output=loss,
349
+ )
350
+
351
+ # Add penalization term enforcing constant volume
352
+ fem.integrate(
353
+ volume_form,
354
+ fields={"level_set": advected_level_set},
355
+ values={"smoothing": self._smoothing},
356
+ domain=self._p_test.domain,
357
+ output=vol,
358
+ )
359
+
360
+ print("Total inflow", loss, "Volume", vol)
361
+
362
+ vol_loss_weight = 1000.0
363
+ wp.launch(
364
+ combine_losses,
365
+ dim=1,
366
+ inputs=(loss, vol, self._target_vol, vol_loss_weight),
367
+ )
368
+
369
+ # perform backward step
370
+ tape.backward(loss=loss)
371
+
372
+ # Advect level set with velocity field adjoint
373
+ v.assign(v.grad)
374
+ self.advect_level_set(
375
+ level_set_in=advected_level_set,
376
+ level_set_out=self._level_set_field,
377
+ velocity=self._level_set_velocity_field,
378
+ dt=-self._dt,
379
+ )
380
+
381
+ # Zero-out gradients used in tape
382
+ tape.zero()
383
+
384
+ def advect_level_set(self, level_set_in: fem.Field, level_set_out: fem.Field, velocity: fem.Field, dt: float):
385
+ if self._discontinuous:
386
+ # Discontinuous Galerkin version with (explicit) upwind transport:
387
+ # Integrate in-cell transport + side flux
388
+ transport_integrals = wp.empty(
389
+ shape=self._ls_space.node_count(),
390
+ dtype=float,
391
+ requires_grad=True,
392
+ )
393
+ fem.integrate(
394
+ level_set_transport_form,
395
+ fields={"psi": self._ls_test, "level_set": level_set_in, "velocity": velocity},
396
+ values={"dt": dt},
397
+ output=transport_integrals,
398
+ )
399
+
400
+ fem.integrate(
401
+ level_set_transport_form_upwind,
402
+ fields={"level_set": level_set_in.trace(), "psi": self._ls_sides_test, "velocity": velocity.trace()},
403
+ values={"dt": dt},
404
+ output=transport_integrals,
405
+ add=True,
406
+ )
407
+
408
+ # Divide by mass matrix and write back to advected field
409
+ out_field = level_set_out if isinstance(level_set_out, fem.DiscreteField) else level_set_out.field
410
+ fem.interpolate(
411
+ advected_level_set_upwind,
412
+ fields={"level_set": level_set_in, "velocity": velocity},
413
+ values={"transport_integrals": transport_integrals},
414
+ quadrature=fem.NodalQuadrature(self._p_test.domain, self._ls_space),
415
+ dest=out_field.dof_values,
416
+ )
417
+ else:
418
+ # Continuous Galerkin version with semi-Lagrangian transport:
419
+ fem.interpolate(
420
+ advected_level_set_semi_lagrangian,
421
+ fields={"level_set": level_set_in, "velocity": velocity},
422
+ values={"dt": dt},
423
+ dest=level_set_out,
424
+ )
425
+
426
+ def render(self):
427
+ # velocity field
428
+ u_space = fem.make_polynomial_space(self._geo, degree=self._p_space.degree, dtype=wp.vec2)
429
+ u_field = fem.make_discrete_field(space=u_space)
430
+ fem.interpolate(
431
+ velocity_field,
432
+ fields={"level_set": self._level_set_field, "p": self._p_field},
433
+ values={"smoothing": self._smoothing},
434
+ dest=u_field,
435
+ )
436
+
437
+ # material fraction field
438
+ mat_field = fem.make_discrete_field(space=self._ls_space)
439
+ fem.interpolate(
440
+ material_fraction,
441
+ fields={"level_set": self._level_set_field},
442
+ values={"smoothing": self._smoothing},
443
+ dest=mat_field,
444
+ )
445
+
446
+ self.renderer.add_field("velocity", u_field)
447
+ self.renderer.add_field("material", mat_field)
448
+
449
+
450
+ if __name__ == "__main__":
451
+ import argparse
452
+
453
+ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
454
+ parser.add_argument("--device", type=str, default=None, help="Override the default Warp device.")
455
+ parser.add_argument("--resolution", type=int, default=100, help="Grid resolution.")
456
+ parser.add_argument("--degree", type=int, default=1, help="Polynomial degree of shape functions.")
457
+ parser.add_argument("--discontinuous", action="store_true", help="Use discontinuous level set advection.")
458
+ parser.add_argument("--mesh", type=str, default="grid", help="Mesh type.")
459
+ parser.add_argument("--num_iters", type=int, default=100, help="Number of iterations.")
460
+ parser.add_argument("--dt", type=float, default=0.05, help="Level set update time step.")
461
+ parser.add_argument(
462
+ "--headless",
463
+ action="store_true",
464
+ help="Run in headless mode, suppressing the opening of any graphical windows.",
465
+ )
466
+
467
+ args = parser.parse_known_args()[0]
468
+
469
+ with wp.ScopedDevice(args.device):
470
+ example = Example(
471
+ quiet=True,
472
+ degree=args.degree,
473
+ resolution=args.resolution,
474
+ discontinuous=args.discontinuous,
475
+ mesh=args.mesh,
476
+ dt=args.dt,
477
+ )
478
+
479
+ for _iter in range(args.num_iters):
480
+ example.step()
481
+ example.render()
482
+
483
+ if not args.headless:
484
+ example.renderer.plot(
485
+ options={
486
+ "velocity": {"arrows": {"glyph_scale": 0.1}},
487
+ "material": {"contours": {"levels": [0.0, 0.5, 1.0001]}},
488
+ },
489
+ )
@@ -119,7 +119,7 @@ class Example:
119
119
  bd_test = fem.make_test(space=self._scalar_space, domain=boundary)
120
120
  bd_trial = fem.make_trial(space=self._scalar_space, domain=boundary)
121
121
 
122
- bd_matrix = fem.integrate(boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, nodal=True)
122
+ bd_matrix = fem.integrate(boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, assembly="nodal")
123
123
 
124
124
  fem.project_linear_system(matrix, rhs, bd_matrix)
125
125
 
@@ -125,9 +125,9 @@ class Example:
125
125
  bd_test = fem.make_test(space=self._scalar_space, domain=boundary)
126
126
  bd_trial = fem.make_trial(space=self._scalar_space, domain=boundary)
127
127
 
128
- bd_matrix = fem.integrate(y_boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, nodal=True)
128
+ bd_matrix = fem.integrate(y_boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, assembly="nodal")
129
129
  bd_rhs = fem.integrate(
130
- y_boundary_value_form, fields={"v": bd_test}, values={"val": self._boundary_value}, nodal=True
130
+ y_boundary_value_form, fields={"v": bd_test}, values={"val": self._boundary_value}, assembly="nodal"
131
131
  )
132
132
 
133
133
  # Assemble linear system
@@ -157,7 +157,7 @@ class Example:
157
157
  boundary_projector_form = (
158
158
  vertical_boundary_projector_form if self._geo.cell_dimension == 3 else y_boundary_projector_form
159
159
  )
160
- bd_matrix = fem.integrate(boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, nodal=True)
160
+ bd_matrix = fem.integrate(boundary_projector_form, fields={"u": bd_trial, "v": bd_test}, assembly="nodal")
161
161
 
162
162
  # Diffusion form
163
163
  trial = fem.make_trial(space=self._scalar_space, domain=domain)
@@ -158,7 +158,7 @@ class Example:
158
158
  u_bd_test = fem.make_test(space=self._u_space, domain=boundary)
159
159
  u_bd_trial = fem.make_trial(space=self._u_space, domain=boundary)
160
160
  u_bd_matrix = fem.integrate(
161
- boundary_projector_form, fields={"u": u_bd_trial, "v": u_bd_test}, nodal=True, output_dtype=float
161
+ boundary_projector_form, fields={"u": u_bd_trial, "v": u_bd_test}, assembly="nodal", output_dtype=float
162
162
  )
163
163
  fem.normalize_dirichlet_projector(u_bd_matrix)
164
164