warp-lang 0.11.0__py3-none-manylinux2014_x86_64.whl → 1.0.0__py3-none-manylinux2014_x86_64.whl

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

Potentially problematic release.


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

Files changed (170) hide show
  1. warp/__init__.py +8 -0
  2. warp/bin/warp-clang.so +0 -0
  3. warp/bin/warp.so +0 -0
  4. warp/build.py +7 -6
  5. warp/build_dll.py +70 -79
  6. warp/builtins.py +10 -6
  7. warp/codegen.py +51 -19
  8. warp/config.py +7 -8
  9. warp/constants.py +3 -0
  10. warp/context.py +948 -245
  11. warp/dlpack.py +198 -113
  12. warp/examples/assets/bunny.usd +0 -0
  13. warp/examples/assets/cartpole.urdf +110 -0
  14. warp/examples/assets/crazyflie.usd +0 -0
  15. warp/examples/assets/cube.usda +42 -0
  16. warp/examples/assets/nv_ant.xml +92 -0
  17. warp/examples/assets/nv_humanoid.xml +183 -0
  18. warp/examples/assets/quadruped.urdf +268 -0
  19. warp/examples/assets/rocks.nvdb +0 -0
  20. warp/examples/assets/rocks.usd +0 -0
  21. warp/examples/assets/sphere.usda +56 -0
  22. warp/examples/assets/torus.usda +105 -0
  23. warp/examples/benchmarks/benchmark_api.py +383 -0
  24. warp/examples/benchmarks/benchmark_cloth.py +279 -0
  25. warp/examples/benchmarks/benchmark_cloth_cupy.py +88 -0
  26. warp/examples/benchmarks/benchmark_cloth_jax.py +100 -0
  27. warp/examples/benchmarks/benchmark_cloth_numba.py +142 -0
  28. warp/examples/benchmarks/benchmark_cloth_numpy.py +77 -0
  29. warp/examples/benchmarks/benchmark_cloth_pytorch.py +86 -0
  30. warp/examples/benchmarks/benchmark_cloth_taichi.py +112 -0
  31. warp/examples/benchmarks/benchmark_cloth_warp.py +146 -0
  32. warp/examples/benchmarks/benchmark_launches.py +295 -0
  33. warp/examples/core/example_dem.py +221 -0
  34. warp/examples/core/example_fluid.py +267 -0
  35. warp/examples/core/example_graph_capture.py +129 -0
  36. warp/examples/core/example_marching_cubes.py +177 -0
  37. warp/examples/core/example_mesh.py +154 -0
  38. warp/examples/core/example_mesh_intersect.py +193 -0
  39. warp/examples/core/example_nvdb.py +169 -0
  40. warp/examples/core/example_raycast.py +89 -0
  41. warp/examples/core/example_raymarch.py +178 -0
  42. warp/examples/core/example_render_opengl.py +141 -0
  43. warp/examples/core/example_sph.py +389 -0
  44. warp/examples/core/example_torch.py +181 -0
  45. warp/examples/core/example_wave.py +249 -0
  46. warp/examples/fem/bsr_utils.py +380 -0
  47. warp/examples/fem/example_apic_fluid.py +391 -0
  48. warp/examples/fem/example_convection_diffusion.py +168 -0
  49. warp/examples/fem/example_convection_diffusion_dg.py +209 -0
  50. warp/examples/fem/example_convection_diffusion_dg0.py +194 -0
  51. warp/examples/fem/example_deformed_geometry.py +159 -0
  52. warp/examples/fem/example_diffusion.py +173 -0
  53. warp/examples/fem/example_diffusion_3d.py +152 -0
  54. warp/examples/fem/example_diffusion_mgpu.py +214 -0
  55. warp/examples/fem/example_mixed_elasticity.py +222 -0
  56. warp/examples/fem/example_navier_stokes.py +243 -0
  57. warp/examples/fem/example_stokes.py +192 -0
  58. warp/examples/fem/example_stokes_transfer.py +249 -0
  59. warp/examples/fem/mesh_utils.py +109 -0
  60. warp/examples/fem/plot_utils.py +287 -0
  61. warp/examples/optim/example_bounce.py +248 -0
  62. warp/examples/optim/example_cloth_throw.py +210 -0
  63. warp/examples/optim/example_diffray.py +535 -0
  64. warp/examples/optim/example_drone.py +850 -0
  65. warp/examples/optim/example_inverse_kinematics.py +169 -0
  66. warp/examples/optim/example_inverse_kinematics_torch.py +170 -0
  67. warp/examples/optim/example_spring_cage.py +234 -0
  68. warp/examples/optim/example_trajectory.py +201 -0
  69. warp/examples/sim/example_cartpole.py +128 -0
  70. warp/examples/sim/example_cloth.py +184 -0
  71. warp/examples/sim/example_granular.py +113 -0
  72. warp/examples/sim/example_granular_collision_sdf.py +185 -0
  73. warp/examples/sim/example_jacobian_ik.py +213 -0
  74. warp/examples/sim/example_particle_chain.py +106 -0
  75. warp/examples/sim/example_quadruped.py +179 -0
  76. warp/examples/sim/example_rigid_chain.py +191 -0
  77. warp/examples/sim/example_rigid_contact.py +176 -0
  78. warp/examples/sim/example_rigid_force.py +126 -0
  79. warp/examples/sim/example_rigid_gyroscopic.py +97 -0
  80. warp/examples/sim/example_rigid_soft_contact.py +124 -0
  81. warp/examples/sim/example_soft_body.py +178 -0
  82. warp/fabric.py +29 -20
  83. warp/fem/cache.py +0 -1
  84. warp/fem/dirichlet.py +0 -2
  85. warp/fem/integrate.py +0 -1
  86. warp/jax.py +45 -0
  87. warp/jax_experimental.py +339 -0
  88. warp/native/builtin.h +12 -0
  89. warp/native/bvh.cu +18 -18
  90. warp/native/clang/clang.cpp +8 -3
  91. warp/native/cuda_util.cpp +94 -5
  92. warp/native/cuda_util.h +35 -6
  93. warp/native/cutlass_gemm.cpp +1 -1
  94. warp/native/cutlass_gemm.cu +4 -1
  95. warp/native/error.cpp +66 -0
  96. warp/native/error.h +27 -0
  97. warp/native/mesh.cu +2 -2
  98. warp/native/reduce.cu +4 -4
  99. warp/native/runlength_encode.cu +2 -2
  100. warp/native/scan.cu +2 -2
  101. warp/native/sparse.cu +0 -1
  102. warp/native/temp_buffer.h +2 -2
  103. warp/native/warp.cpp +95 -60
  104. warp/native/warp.cu +1053 -218
  105. warp/native/warp.h +49 -32
  106. warp/optim/linear.py +33 -16
  107. warp/render/render_opengl.py +202 -101
  108. warp/render/render_usd.py +82 -40
  109. warp/sim/__init__.py +13 -4
  110. warp/sim/articulation.py +4 -5
  111. warp/sim/collide.py +320 -175
  112. warp/sim/import_mjcf.py +25 -30
  113. warp/sim/import_urdf.py +94 -63
  114. warp/sim/import_usd.py +51 -36
  115. warp/sim/inertia.py +3 -2
  116. warp/sim/integrator.py +233 -0
  117. warp/sim/integrator_euler.py +447 -469
  118. warp/sim/integrator_featherstone.py +1991 -0
  119. warp/sim/integrator_xpbd.py +1420 -640
  120. warp/sim/model.py +765 -487
  121. warp/sim/particles.py +2 -1
  122. warp/sim/render.py +35 -13
  123. warp/sim/utils.py +222 -11
  124. warp/stubs.py +8 -0
  125. warp/tape.py +16 -1
  126. warp/tests/aux_test_grad_customs.py +23 -0
  127. warp/tests/test_array.py +190 -1
  128. warp/tests/test_async.py +656 -0
  129. warp/tests/test_bool.py +50 -0
  130. warp/tests/test_dlpack.py +164 -11
  131. warp/tests/test_examples.py +166 -74
  132. warp/tests/test_fem.py +8 -1
  133. warp/tests/test_generics.py +15 -5
  134. warp/tests/test_grad.py +1 -1
  135. warp/tests/test_grad_customs.py +172 -12
  136. warp/tests/test_jax.py +254 -0
  137. warp/tests/test_large.py +29 -6
  138. warp/tests/test_launch.py +25 -0
  139. warp/tests/test_linear_solvers.py +20 -3
  140. warp/tests/test_matmul.py +61 -16
  141. warp/tests/test_matmul_lite.py +13 -13
  142. warp/tests/test_mempool.py +186 -0
  143. warp/tests/test_multigpu.py +3 -0
  144. warp/tests/test_options.py +16 -2
  145. warp/tests/test_peer.py +137 -0
  146. warp/tests/test_print.py +3 -1
  147. warp/tests/test_quat.py +23 -0
  148. warp/tests/test_sim_kinematics.py +97 -0
  149. warp/tests/test_snippet.py +126 -3
  150. warp/tests/test_streams.py +108 -79
  151. warp/tests/test_torch.py +16 -8
  152. warp/tests/test_utils.py +32 -27
  153. warp/tests/test_verify_fp.py +65 -0
  154. warp/tests/test_volume.py +1 -1
  155. warp/tests/unittest_serial.py +2 -0
  156. warp/tests/unittest_suites.py +12 -0
  157. warp/tests/unittest_utils.py +14 -7
  158. warp/thirdparty/unittest_parallel.py +15 -3
  159. warp/torch.py +10 -8
  160. warp/types.py +363 -246
  161. warp/utils.py +143 -19
  162. warp_lang-1.0.0.dist-info/LICENSE.md +126 -0
  163. warp_lang-1.0.0.dist-info/METADATA +394 -0
  164. {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/RECORD +167 -86
  165. warp/sim/optimizer.py +0 -138
  166. warp_lang-0.11.0.dist-info/LICENSE.md +0 -36
  167. warp_lang-0.11.0.dist-info/METADATA +0 -238
  168. /warp/tests/{walkthough_debug.py → walkthrough_debug.py} +0 -0
  169. {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/WHEEL +0 -0
  170. {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,249 @@
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 Stokes Transfer
10
+ #
11
+ # This example computes a 2D weakly-compressible Stokes flow around
12
+ # a moving object, including:
13
+ # - defining active cells from a mask, and restricting the computation domain to those
14
+ # - utilizing the PicQuadrature to integrate over unstructured particles
15
+ ###########################################################################
16
+
17
+ import math
18
+ import warp as wp
19
+ import numpy as np
20
+
21
+ import warp.fem as fem
22
+
23
+ from warp.utils import array_cast
24
+ from warp.fem.utils import array_axpy
25
+ from warp.sparse import bsr_transposed, bsr_mm, bsr_axpy, bsr_mv
26
+
27
+ # Import example utilities
28
+ # Make sure that works both when imported as module and run as standalone file
29
+ try:
30
+ from .bsr_utils import bsr_cg
31
+ from .plot_utils import Plot
32
+ except ImportError:
33
+ from bsr_utils import bsr_cg
34
+ from plot_utils import Plot
35
+
36
+ wp.init()
37
+
38
+
39
+ @fem.integrand
40
+ def vel_from_particles_form(s: fem.Sample, particle_vel: wp.array(dtype=wp.vec2), v: fem.Field):
41
+ vel = particle_vel[s.qp_index]
42
+ return wp.dot(vel, v(s))
43
+
44
+
45
+ @fem.integrand
46
+ def viscosity_form(s: fem.Sample, u: fem.Field, v: fem.Field, nu: float):
47
+ return nu * wp.ddot(fem.D(u, s), fem.D(v, s))
48
+
49
+
50
+ @fem.integrand
51
+ def mass_form(
52
+ s: fem.Sample,
53
+ u: fem.Field,
54
+ v: fem.Field,
55
+ ):
56
+ return wp.dot(u(s), v(s))
57
+
58
+
59
+ @fem.integrand
60
+ def scalar_mass_form(
61
+ s: fem.Sample,
62
+ p: fem.Field,
63
+ q: fem.Field,
64
+ ):
65
+ return p(s) * q(s)
66
+
67
+
68
+ @fem.integrand
69
+ def div_form(
70
+ s: fem.Sample,
71
+ u: fem.Field,
72
+ q: fem.Field,
73
+ ):
74
+ return q(s) * fem.div(u, s)
75
+
76
+
77
+ @fem.integrand
78
+ def cell_activity(s: fem.Sample, domain: fem.Domain, c1: wp.vec2, c2: wp.vec2, radius: float):
79
+ pos = domain(s)
80
+ if wp.length(pos - c1) < radius:
81
+ return 0.0
82
+ if wp.length(pos - c2) < radius:
83
+ return 0.0
84
+ return 1.0
85
+
86
+
87
+ @wp.kernel
88
+ def inverse_array_kernel(m: wp.array(dtype=wp.float64)):
89
+ m[wp.tid()] = wp.float64(1.0) / m[wp.tid()]
90
+
91
+
92
+ class Example:
93
+ def __init__(self, stage=None, quiet=False):
94
+ self._quiet = quiet
95
+
96
+ self.res = 50
97
+ self.cell_size = 1.0 / self.res
98
+
99
+ self.vel = 1.0
100
+ self.viscosity = 100.0
101
+ self.compliance = 0.01
102
+ self.bd_strength = 100000.0
103
+
104
+ geo = fem.Grid2D(res=wp.vec2i(self.res))
105
+
106
+ # Displacement boundary conditions are defined by two circles going in opposite directions
107
+ # Sample particles along those
108
+ circle_radius = 0.15
109
+ c1_center = wp.vec2(0.25, 0.5)
110
+ c2_center = wp.vec2(0.75, 0.5)
111
+ particles, particle_areas, particle_velocities = self._gen_particles(circle_radius, c1_center, c2_center)
112
+
113
+ # Disable cells that are interior to the circles
114
+ cell_space = fem.make_polynomial_space(geo, degree=0)
115
+ activity = cell_space.make_field()
116
+ fem.interpolate(
117
+ cell_activity,
118
+ dest=activity,
119
+ values={"c1": c1_center, "c2": c2_center, "radius": circle_radius - self.cell_size},
120
+ )
121
+
122
+ # Explicitly define the active geometry partition from those cells
123
+ self._active_partition = fem.ExplicitGeometryPartition(geo, wp.array(activity.dof_values.numpy(), dtype=int))
124
+ if not self._quiet:
125
+ print("Active cells:", self._active_partition.cell_count())
126
+
127
+ # Function spaces -- Q1 for vel, Q0 for pressure
128
+ u_space = fem.make_polynomial_space(geo, degree=1, dtype=wp.vec2)
129
+ p_space = fem.make_polynomial_space(geo, degree=0)
130
+
131
+ self._active_space_partition = fem.make_space_partition(
132
+ space=u_space, geometry_partition=self._active_partition
133
+ )
134
+ self._active_p_space_partition = fem.make_space_partition(
135
+ space=p_space, geometry_partition=self._active_partition
136
+ )
137
+
138
+ self._u_field = u_space.make_field()
139
+ self._p_field = p_space.make_field()
140
+
141
+ # Particle-based quadrature rule over active cells
142
+ domain = fem.Cells(geometry=self._active_partition)
143
+ self._pic_quadrature = fem.PicQuadrature(domain, particles, particle_areas)
144
+ self._particle_velocities = particle_velocities
145
+
146
+ self.renderer = Plot(stage)
147
+
148
+ def step(self):
149
+ u_space = self._u_field.space
150
+ p_space = self._p_field.space
151
+
152
+ # Weakly-enforced boundary condition on particles
153
+ u_test = fem.make_test(space=u_space, space_partition=self._active_space_partition)
154
+ u_trial = fem.make_trial(space=u_space, space_partition=self._active_space_partition)
155
+
156
+ u_rhs = fem.integrate(
157
+ vel_from_particles_form,
158
+ quadrature=self._pic_quadrature,
159
+ fields={"v": u_test},
160
+ values={"particle_vel": self._particle_velocities},
161
+ output_dtype=wp.vec2d,
162
+ )
163
+ u_bd_matrix = fem.integrate(mass_form, quadrature=self._pic_quadrature, fields={"u": u_trial, "v": u_test})
164
+
165
+ # Viscosity
166
+ u_visc_matrix = fem.integrate(
167
+ viscosity_form,
168
+ fields={"u": u_trial, "v": u_test},
169
+ values={"nu": self.viscosity},
170
+ )
171
+
172
+ # Pressure-velocity coupling
173
+ p_test = fem.make_test(space=p_space, space_partition=self._active_p_space_partition)
174
+ p_trial = fem.make_trial(space=p_space, space_partition=self._active_p_space_partition)
175
+
176
+ div_matrix = fem.integrate(div_form, fields={"u": u_trial, "q": p_test})
177
+ inv_p_mass_matrix = fem.integrate(scalar_mass_form, fields={"p": p_trial, "q": p_test})
178
+ wp.launch(
179
+ kernel=inverse_array_kernel,
180
+ dim=inv_p_mass_matrix.values.shape,
181
+ device=inv_p_mass_matrix.values.device,
182
+ inputs=[inv_p_mass_matrix.values],
183
+ )
184
+
185
+ # Assemble linear system
186
+ u_matrix = u_visc_matrix
187
+ bsr_axpy(u_bd_matrix, u_matrix, alpha=self.bd_strength)
188
+
189
+ div_matrix_t = bsr_transposed(div_matrix)
190
+ gradient_matrix = bsr_mm(div_matrix_t, inv_p_mass_matrix)
191
+ bsr_mm(gradient_matrix, div_matrix, u_matrix, alpha=1.0 / self.compliance, beta=1.0)
192
+
193
+ array_axpy(u_rhs, u_rhs, alpha=0.0, beta=self.bd_strength)
194
+
195
+ # Solve for displacement
196
+ u_res = wp.zeros_like(u_rhs)
197
+ bsr_cg(u_matrix, x=u_res, b=u_rhs, quiet=self._quiet)
198
+
199
+ # Compute pressure from displacement
200
+ div_u = bsr_mv(A=div_matrix, x=u_res)
201
+ p_res = bsr_mv(A=inv_p_mass_matrix, x=div_u, alpha=-1)
202
+
203
+ # Copy to fields
204
+ u_nodes = wp.indexedarray(self._u_field.dof_values, indices=self._active_space_partition.space_node_indices())
205
+ p_nodes = wp.indexedarray(self._p_field.dof_values, indices=self._active_p_space_partition.space_node_indices())
206
+
207
+ array_cast(in_array=u_res, out_array=u_nodes)
208
+ array_cast(in_array=p_res, out_array=p_nodes)
209
+
210
+ def render(self):
211
+ self.renderer.add_surface("pressure", self._p_field)
212
+ self.renderer.add_surface_vector("velocity", self._u_field)
213
+
214
+ def _gen_particles(self, circle_radius, c1_center, c2_center):
215
+ """Generate some particles along two circles defining velocity boundary conditions"""
216
+
217
+ # Generate particles defining the transfer displacement
218
+ particles_per_circle = int(2.0 * math.pi * circle_radius * self.res)
219
+
220
+ angles = np.linspace(0, 2.0 * math.pi, particles_per_circle, endpoint=False)
221
+
222
+ n_particles = 2 * particles_per_circle
223
+ particles = np.empty((n_particles, 2), dtype=float)
224
+
225
+ particles[:particles_per_circle, 0] = c1_center[0] + circle_radius * np.cos(angles)
226
+ particles[:particles_per_circle, 1] = c1_center[1] + circle_radius * np.sin(angles)
227
+ particles[particles_per_circle:, 0] = c2_center[0] + circle_radius * np.cos(angles)
228
+ particles[particles_per_circle:, 1] = c2_center[1] + circle_radius * np.sin(angles)
229
+
230
+ particle_areas = np.ones(n_particles) * self.cell_size**2
231
+ particle_velocities = np.zeros_like(particles)
232
+ particle_velocities[:particles_per_circle, 0] = self.vel
233
+ particle_velocities[particles_per_circle:, 0] = -self.vel
234
+
235
+ particles = wp.array(particles, dtype=wp.vec2)
236
+ particle_areas = wp.array(particle_areas, dtype=float)
237
+ particle_velocities = wp.array(particle_velocities, dtype=wp.vec2)
238
+
239
+ return particles, particle_areas, particle_velocities
240
+
241
+
242
+ if __name__ == "__main__":
243
+ wp.set_module_options({"enable_backward": False})
244
+
245
+ example = Example()
246
+ example.step()
247
+ example.render()
248
+
249
+ example.renderer.plot(streamlines=["velocity"])
@@ -0,0 +1,109 @@
1
+ import numpy as np
2
+ import warp as wp
3
+
4
+ from warp.fem.utils import grid_to_tets, grid_to_tris, grid_to_quads, grid_to_hexes
5
+
6
+
7
+ def gen_trimesh(res, bounds_lo: wp.vec2 = wp.vec2(0.0), bounds_hi: wp.vec2 = wp.vec2(1.0)):
8
+ """Constructs a triangular mesh by diving each cell of a dense 2D grid into two triangles
9
+
10
+ Args:
11
+ res: Resolution of the grid along each dimension
12
+ bounds_lo: Position of the lower bound of the axis-aligned grid
13
+ bounds_up: Position of the upper bound of the axis-aligned grid
14
+
15
+ Returns:
16
+ Tuple of ndarrays: (Vertex positions, Triangle vertex indices)
17
+ """
18
+
19
+ Nx = res[0]
20
+ Ny = res[1]
21
+
22
+ x = np.linspace(bounds_lo[0], bounds_hi[0], Nx + 1)
23
+ y = np.linspace(bounds_lo[1], bounds_hi[1], Ny + 1)
24
+
25
+ positions = np.transpose(np.meshgrid(x, y, indexing="ij"), axes=(1, 2, 0)).reshape(-1, 2)
26
+
27
+ vidx = grid_to_tris(Nx, Ny)
28
+
29
+ return wp.array(positions, dtype=wp.vec2), wp.array(vidx, dtype=int)
30
+
31
+
32
+ def gen_tetmesh(res, bounds_lo: wp.vec3 = wp.vec3(0.0), bounds_hi: wp.vec3 = wp.vec3(1.0)):
33
+ """Constructs a tetrahedral mesh by diving each cell of a dense 3D grid into five tetrahedrons
34
+
35
+ Args:
36
+ res: Resolution of the grid along each dimension
37
+ bounds_lo: Position of the lower bound of the axis-aligned grid
38
+ bounds_up: Position of the upper bound of the axis-aligned grid
39
+
40
+ Returns:
41
+ Tuple of ndarrays: (Vertex positions, Tetrahedron vertex indices)
42
+ """
43
+
44
+ Nx = res[0]
45
+ Ny = res[1]
46
+ Nz = res[2]
47
+
48
+ x = np.linspace(bounds_lo[0], bounds_hi[0], Nx + 1)
49
+ y = np.linspace(bounds_lo[1], bounds_hi[1], Ny + 1)
50
+ z = np.linspace(bounds_lo[2], bounds_hi[2], Nz + 1)
51
+
52
+ positions = np.transpose(np.meshgrid(x, y, z, indexing="ij"), axes=(1, 2, 3, 0)).reshape(-1, 3)
53
+
54
+ vidx = grid_to_tets(Nx, Ny, Nz)
55
+
56
+ return wp.array(positions, dtype=wp.vec3), wp.array(vidx, dtype=int)
57
+
58
+
59
+ def gen_quadmesh(res, bounds_lo: wp.vec2 = wp.vec2(0.0), bounds_hi: wp.vec2 = wp.vec2(1.0)):
60
+ """Constructs a quadrilateral mesh from a dense 2D grid
61
+
62
+ Args:
63
+ res: Resolution of the grid along each dimension
64
+ bounds_lo: Position of the lower bound of the axis-aligned grid
65
+ bounds_up: Position of the upper bound of the axis-aligned grid
66
+
67
+ Returns:
68
+ Tuple of ndarrays: (Vertex positions, Triangle vertex indices)
69
+ """
70
+
71
+ Nx = res[0]
72
+ Ny = res[1]
73
+
74
+ x = np.linspace(bounds_lo[0], bounds_hi[0], Nx + 1)
75
+ y = np.linspace(bounds_lo[1], bounds_hi[1], Ny + 1)
76
+
77
+ positions = np.transpose(np.meshgrid(x, y, indexing="ij"), axes=(1, 2, 0)).reshape(-1, 2)
78
+
79
+ vidx = grid_to_quads(Nx, Ny)
80
+
81
+ return wp.array(positions, dtype=wp.vec2), wp.array(vidx, dtype=int)
82
+
83
+
84
+ def gen_hexmesh(res, bounds_lo: wp.vec3 = wp.vec3(0.0), bounds_hi: wp.vec3 = wp.vec3(1.0)):
85
+ """Constructs a quadrilateral mesh from a dense 2D grid
86
+
87
+ Args:
88
+ res: Resolution of the grid along each dimension
89
+ bounds_lo: Position of the lower bound of the axis-aligned grid
90
+ bounds_up: Position of the upper bound of the axis-aligned grid
91
+
92
+ Returns:
93
+ Tuple of ndarrays: (Vertex positions, Triangle vertex indices)
94
+ """
95
+
96
+ Nx = res[0]
97
+ Ny = res[1]
98
+ Nz = res[2]
99
+
100
+ x = np.linspace(bounds_lo[0], bounds_hi[0], Nx + 1)
101
+ y = np.linspace(bounds_lo[1], bounds_hi[1], Ny + 1)
102
+ z = np.linspace(bounds_lo[1], bounds_hi[1], Nz + 1)
103
+
104
+ positions = np.transpose(np.meshgrid(x, y, z, indexing="ij"), axes=(1, 2, 3, 0)).reshape(-1, 3)
105
+
106
+ vidx = grid_to_hexes(Nx, Ny, Nz)
107
+
108
+ return wp.array(positions, dtype=wp.vec3), wp.array(vidx, dtype=int)
109
+
@@ -0,0 +1,287 @@
1
+ from typing import Set
2
+
3
+ import numpy as np
4
+
5
+ from warp.fem import DiscreteField
6
+
7
+
8
+ def plot_grid_surface(field, axes=None):
9
+ import matplotlib.pyplot as plt
10
+ from matplotlib import cm
11
+
12
+ if axes is None:
13
+ fig, axes = plt.subplots(subplot_kw={"projection": "3d"})
14
+
15
+ node_positions = field.space.node_grid()
16
+
17
+ # Make data.
18
+ X = node_positions[0]
19
+ Y = node_positions[1]
20
+ Z = field.dof_values.numpy().reshape(X.shape)
21
+
22
+ # Plot the surface.
23
+ return axes.plot_surface(X, Y, Z, cmap=cm.coolwarm, linewidth=0, antialiased=False)
24
+
25
+
26
+ def plot_tri_surface(field, axes=None):
27
+ import matplotlib.pyplot as plt
28
+ from matplotlib import cm
29
+ from matplotlib.tri.triangulation import Triangulation
30
+
31
+ if axes is None:
32
+ fig, axes = plt.subplots(subplot_kw={"projection": "3d"})
33
+
34
+ node_positions = field.space.node_positions().numpy()
35
+
36
+ triangulation = Triangulation(
37
+ x=node_positions[:, 0], y=node_positions[:, 1], triangles=field.space.node_triangulation()
38
+ )
39
+
40
+ Z = field.dof_values.numpy()
41
+
42
+ # Plot the surface.
43
+ return axes.plot_trisurf(triangulation, Z, cmap=cm.coolwarm, linewidth=0, antialiased=False)
44
+
45
+
46
+ def plot_scatter_surface(field, axes=None):
47
+ import matplotlib.pyplot as plt
48
+ from matplotlib import cm
49
+
50
+ if axes is None:
51
+ fig, axes = plt.subplots(subplot_kw={"projection": "3d"})
52
+
53
+ X, Y = field.space.node_positions().numpy().T
54
+
55
+ # Make data.
56
+ Z = field.dof_values.numpy().reshape(X.shape)
57
+
58
+ # Plot the surface.
59
+ return axes.scatter(X, Y, Z, c=Z, cmap=cm.coolwarm)
60
+
61
+
62
+ def plot_surface(field, axes=None):
63
+ if hasattr(field.space, "node_grid"):
64
+ return plot_grid_surface(field, axes)
65
+ elif hasattr(field.space, "node_triangulation"):
66
+ return plot_tri_surface(field, axes)
67
+ else:
68
+ return plot_scatter_surface(field, axes)
69
+
70
+
71
+ def plot_grid_color(field, axes=None):
72
+ import matplotlib.pyplot as plt
73
+ from matplotlib import cm
74
+
75
+ if axes is None:
76
+ fig, axes = plt.subplots()
77
+
78
+ node_positions = field.space.node_grid()
79
+
80
+ # Make data.
81
+ X = node_positions[0]
82
+ Y = node_positions[1]
83
+ Z = field.dof_values.numpy().reshape(X.shape)
84
+
85
+ # Plot the surface.
86
+ return axes.pcolormesh(X, Y, Z, cmap=cm.coolwarm)
87
+
88
+
89
+ def plot_velocities(field, axes=None):
90
+ import matplotlib.pyplot as plt
91
+
92
+ if axes is None:
93
+ fig, axes = plt.subplots()
94
+
95
+ node_positions = field.space.node_positions().numpy()
96
+
97
+ # Make data.
98
+ X = node_positions[:, 0]
99
+ Y = node_positions[:, 1]
100
+
101
+ vel = field.dof_values.numpy()
102
+ u = np.ascontiguousarray(vel[:, 0])
103
+ v = np.ascontiguousarray(vel[:, 1])
104
+
105
+ u = u.reshape(X.shape)
106
+ v = v.reshape(X.shape)
107
+
108
+ return axes.quiver(X, Y, u, v)
109
+
110
+
111
+ def plot_grid_streamlines(field, axes=None):
112
+ import matplotlib.pyplot as plt
113
+
114
+ if axes is None:
115
+ fig, axes = plt.subplots()
116
+
117
+ node_positions = field.space.node_grid()
118
+
119
+ # Make data.
120
+ X = node_positions[0][:, 0]
121
+ Y = node_positions[1][0, :]
122
+
123
+ vel = field.dof_values.numpy()
124
+ u = np.ascontiguousarray(vel[:, 0])
125
+ v = np.ascontiguousarray(vel[:, 1])
126
+
127
+ u = np.transpose(u.reshape(node_positions[0].shape))
128
+ v = np.transpose(v.reshape(node_positions[0].shape))
129
+
130
+ splot = axes.streamplot(X, Y, u, v, density=2)
131
+ splot.axes = axes
132
+ return splot
133
+
134
+
135
+ def plot_3d_scatter(field, axes=None):
136
+ import matplotlib.pyplot as plt
137
+ from matplotlib import cm
138
+
139
+ if axes is None:
140
+ fig, axes = plt.subplots(subplot_kw={"projection": "3d"})
141
+
142
+ X, Y, Z = field.space.node_positions().numpy().T
143
+
144
+ # Make data.
145
+ f = field.dof_values.numpy().reshape(X.shape)
146
+
147
+ # Plot the surface.
148
+ return axes.scatter(X, Y, Z, c=f, cmap=cm.coolwarm)
149
+
150
+
151
+ def plot_3d_velocities(field, axes=None):
152
+ import matplotlib.pyplot as plt
153
+
154
+ if axes is None:
155
+ fig, axes = plt.subplots(subplot_kw={"projection": "3d"})
156
+
157
+ X, Y, Z = field.space.node_positions().numpy().T
158
+
159
+ vel = field.dof_values.numpy()
160
+ u = np.ascontiguousarray(vel[:, 0])
161
+ v = np.ascontiguousarray(vel[:, 1])
162
+ w = np.ascontiguousarray(vel[:, 2])
163
+
164
+ u = u.reshape(X.shape)
165
+ v = v.reshape(X.shape)
166
+ w = w.reshape(X.shape)
167
+
168
+ return axes.quiver(X, Y, Z, u, v, w, length=1.0 / X.shape[0], normalize=False)
169
+
170
+
171
+ class Plot:
172
+ def __init__(self, stage=None, default_point_radius=0.01):
173
+ self.default_point_radius = default_point_radius
174
+
175
+ self._surfaces = {}
176
+ self._surface_vectors = {}
177
+ self._volumes = {}
178
+
179
+ self._usd_renderer = None
180
+ if stage is not None:
181
+ try:
182
+ from warp.render import UsdRenderer
183
+
184
+ self._usd_renderer = UsdRenderer(stage)
185
+ except Exception as err:
186
+ print(f"Could not initialize UsdRenderer for stage '{stage}': {err}.")
187
+
188
+ def begin_frame(self, time):
189
+ if self._usd_renderer is not None:
190
+ self._usd_renderer.begin_frame(time=time)
191
+
192
+ def end_frame(self):
193
+ if self._usd_renderer is not None:
194
+ self._usd_renderer.end_frame()
195
+
196
+ def add_surface(self, name: str, field: DiscreteField):
197
+ if self._usd_renderer is not None:
198
+ points_2d = field.space.node_positions().numpy()
199
+ values = field.dof_values.numpy()
200
+ points_3d = np.hstack((points_2d, values.reshape(-1, 1)))
201
+
202
+ if hasattr(field.space, "node_triangulation"):
203
+ indices = field.space.node_triangulation()
204
+ self._usd_renderer.render_mesh(name, points=points_3d, indices=indices)
205
+ else:
206
+ self._usd_renderer.render_points(name, points=points_3d, radius=self.default_point_radius)
207
+
208
+ if name not in self._surfaces:
209
+ field_clone = field.space.make_field(space_partition=field.space_partition)
210
+ self._surfaces[name] = (field_clone, [])
211
+
212
+ self._surfaces[name][1].append(field.dof_values.numpy())
213
+
214
+ def add_surface_vector(self, name: str, field: DiscreteField):
215
+ if self._usd_renderer is not None:
216
+ points_2d = field.space.node_positions().numpy()
217
+ values = field.dof_values.numpy()
218
+ points_3d = np.hstack((points_2d + values, np.zeros_like(points_2d[:, 0]).reshape(-1, 1)))
219
+
220
+ if hasattr(field.space, "node_triangulation"):
221
+ indices = field.space.node_triangulation()
222
+ self._usd_renderer.render_mesh(name, points=points_3d, indices=indices)
223
+ else:
224
+ self._usd_renderer.render_points(name, points=points_3d, radius=self.default_point_radius)
225
+
226
+ if name not in self._surface_vectors:
227
+ field_clone = field.space.make_field(space_partition=field.space_partition)
228
+ self._surface_vectors[name] = (field_clone, [])
229
+
230
+ self._surface_vectors[name][1].append(field.dof_values.numpy())
231
+
232
+ def add_volume(self, name: str, field: DiscreteField):
233
+ if self._usd_renderer is not None:
234
+ points_3d = field.space.node_positions().numpy()
235
+ values = field.dof_values.numpy()
236
+
237
+ self._usd_renderer.render_points(name, points_3d, radius=values)
238
+
239
+ if name not in self._volumes:
240
+ field_clone = field.space.make_field(space_partition=field.space_partition)
241
+ self._volumes[name] = (field_clone, [])
242
+
243
+ self._volumes[name][1].append(field.dof_values.numpy())
244
+
245
+ def plot(self, streamlines: Set[str] = []):
246
+ return self._plot_matplotlib(streamlines)
247
+
248
+ def _plot_matplotlib(self, streamlines: Set[str] = []):
249
+ import matplotlib.pyplot as plt
250
+ import matplotlib.animation as animation
251
+
252
+ def make_animation(ax, field, values, plot_func, num_frames: int):
253
+ def animate(i):
254
+ ax.clear()
255
+ field.dof_values = values[i]
256
+ return plot_func(field, axes=ax)
257
+
258
+ return animation.FuncAnimation(
259
+ ax.figure,
260
+ animate,
261
+ interval=30,
262
+ blit=False,
263
+ frames=len(values),
264
+ )
265
+
266
+ for name, (field, values) in self._surfaces.items():
267
+ field.dof_values = values[0]
268
+ ax = plot_surface(field).axes
269
+
270
+ if len(values) > 1:
271
+ anim = make_animation(ax, field, values, plot_func=plot_surface, num_frames=len(values))
272
+
273
+ for name, (field, values) in self._surface_vectors.items():
274
+ field.dof_values = values[0]
275
+ if name in streamlines and hasattr(field.space, "node_grid"):
276
+ ax = plot_grid_streamlines(field).axes
277
+ else:
278
+ ax = plot_velocities(field).axes
279
+
280
+ if len(values) > 1:
281
+ anim = make_animation(ax, field, values, plot_func=plot_velocities, num_frames=len(values))
282
+
283
+ for name, (field, values) in self._volumes.items():
284
+ field.dof_values = values[0]
285
+ ax = plot_3d_scatter(field).axes
286
+
287
+ plt.show()