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.
- warp/__init__.py +8 -0
- warp/bin/warp-clang.so +0 -0
- warp/bin/warp.so +0 -0
- warp/build.py +7 -6
- warp/build_dll.py +70 -79
- warp/builtins.py +10 -6
- warp/codegen.py +51 -19
- warp/config.py +7 -8
- warp/constants.py +3 -0
- warp/context.py +948 -245
- warp/dlpack.py +198 -113
- warp/examples/assets/bunny.usd +0 -0
- warp/examples/assets/cartpole.urdf +110 -0
- warp/examples/assets/crazyflie.usd +0 -0
- warp/examples/assets/cube.usda +42 -0
- warp/examples/assets/nv_ant.xml +92 -0
- warp/examples/assets/nv_humanoid.xml +183 -0
- warp/examples/assets/quadruped.urdf +268 -0
- warp/examples/assets/rocks.nvdb +0 -0
- warp/examples/assets/rocks.usd +0 -0
- warp/examples/assets/sphere.usda +56 -0
- warp/examples/assets/torus.usda +105 -0
- warp/examples/benchmarks/benchmark_api.py +383 -0
- warp/examples/benchmarks/benchmark_cloth.py +279 -0
- warp/examples/benchmarks/benchmark_cloth_cupy.py +88 -0
- warp/examples/benchmarks/benchmark_cloth_jax.py +100 -0
- warp/examples/benchmarks/benchmark_cloth_numba.py +142 -0
- warp/examples/benchmarks/benchmark_cloth_numpy.py +77 -0
- warp/examples/benchmarks/benchmark_cloth_pytorch.py +86 -0
- warp/examples/benchmarks/benchmark_cloth_taichi.py +112 -0
- warp/examples/benchmarks/benchmark_cloth_warp.py +146 -0
- warp/examples/benchmarks/benchmark_launches.py +295 -0
- warp/examples/core/example_dem.py +221 -0
- warp/examples/core/example_fluid.py +267 -0
- warp/examples/core/example_graph_capture.py +129 -0
- warp/examples/core/example_marching_cubes.py +177 -0
- warp/examples/core/example_mesh.py +154 -0
- warp/examples/core/example_mesh_intersect.py +193 -0
- warp/examples/core/example_nvdb.py +169 -0
- warp/examples/core/example_raycast.py +89 -0
- warp/examples/core/example_raymarch.py +178 -0
- warp/examples/core/example_render_opengl.py +141 -0
- warp/examples/core/example_sph.py +389 -0
- warp/examples/core/example_torch.py +181 -0
- warp/examples/core/example_wave.py +249 -0
- warp/examples/fem/bsr_utils.py +380 -0
- warp/examples/fem/example_apic_fluid.py +391 -0
- warp/examples/fem/example_convection_diffusion.py +168 -0
- warp/examples/fem/example_convection_diffusion_dg.py +209 -0
- warp/examples/fem/example_convection_diffusion_dg0.py +194 -0
- warp/examples/fem/example_deformed_geometry.py +159 -0
- warp/examples/fem/example_diffusion.py +173 -0
- warp/examples/fem/example_diffusion_3d.py +152 -0
- warp/examples/fem/example_diffusion_mgpu.py +214 -0
- warp/examples/fem/example_mixed_elasticity.py +222 -0
- warp/examples/fem/example_navier_stokes.py +243 -0
- warp/examples/fem/example_stokes.py +192 -0
- warp/examples/fem/example_stokes_transfer.py +249 -0
- warp/examples/fem/mesh_utils.py +109 -0
- warp/examples/fem/plot_utils.py +287 -0
- warp/examples/optim/example_bounce.py +248 -0
- warp/examples/optim/example_cloth_throw.py +210 -0
- warp/examples/optim/example_diffray.py +535 -0
- warp/examples/optim/example_drone.py +850 -0
- warp/examples/optim/example_inverse_kinematics.py +169 -0
- warp/examples/optim/example_inverse_kinematics_torch.py +170 -0
- warp/examples/optim/example_spring_cage.py +234 -0
- warp/examples/optim/example_trajectory.py +201 -0
- warp/examples/sim/example_cartpole.py +128 -0
- warp/examples/sim/example_cloth.py +184 -0
- warp/examples/sim/example_granular.py +113 -0
- warp/examples/sim/example_granular_collision_sdf.py +185 -0
- warp/examples/sim/example_jacobian_ik.py +213 -0
- warp/examples/sim/example_particle_chain.py +106 -0
- warp/examples/sim/example_quadruped.py +179 -0
- warp/examples/sim/example_rigid_chain.py +191 -0
- warp/examples/sim/example_rigid_contact.py +176 -0
- warp/examples/sim/example_rigid_force.py +126 -0
- warp/examples/sim/example_rigid_gyroscopic.py +97 -0
- warp/examples/sim/example_rigid_soft_contact.py +124 -0
- warp/examples/sim/example_soft_body.py +178 -0
- warp/fabric.py +29 -20
- warp/fem/cache.py +0 -1
- warp/fem/dirichlet.py +0 -2
- warp/fem/integrate.py +0 -1
- warp/jax.py +45 -0
- warp/jax_experimental.py +339 -0
- warp/native/builtin.h +12 -0
- warp/native/bvh.cu +18 -18
- warp/native/clang/clang.cpp +8 -3
- warp/native/cuda_util.cpp +94 -5
- warp/native/cuda_util.h +35 -6
- warp/native/cutlass_gemm.cpp +1 -1
- warp/native/cutlass_gemm.cu +4 -1
- warp/native/error.cpp +66 -0
- warp/native/error.h +27 -0
- warp/native/mesh.cu +2 -2
- warp/native/reduce.cu +4 -4
- warp/native/runlength_encode.cu +2 -2
- warp/native/scan.cu +2 -2
- warp/native/sparse.cu +0 -1
- warp/native/temp_buffer.h +2 -2
- warp/native/warp.cpp +95 -60
- warp/native/warp.cu +1053 -218
- warp/native/warp.h +49 -32
- warp/optim/linear.py +33 -16
- warp/render/render_opengl.py +202 -101
- warp/render/render_usd.py +82 -40
- warp/sim/__init__.py +13 -4
- warp/sim/articulation.py +4 -5
- warp/sim/collide.py +320 -175
- warp/sim/import_mjcf.py +25 -30
- warp/sim/import_urdf.py +94 -63
- warp/sim/import_usd.py +51 -36
- warp/sim/inertia.py +3 -2
- warp/sim/integrator.py +233 -0
- warp/sim/integrator_euler.py +447 -469
- warp/sim/integrator_featherstone.py +1991 -0
- warp/sim/integrator_xpbd.py +1420 -640
- warp/sim/model.py +765 -487
- warp/sim/particles.py +2 -1
- warp/sim/render.py +35 -13
- warp/sim/utils.py +222 -11
- warp/stubs.py +8 -0
- warp/tape.py +16 -1
- warp/tests/aux_test_grad_customs.py +23 -0
- warp/tests/test_array.py +190 -1
- warp/tests/test_async.py +656 -0
- warp/tests/test_bool.py +50 -0
- warp/tests/test_dlpack.py +164 -11
- warp/tests/test_examples.py +166 -74
- warp/tests/test_fem.py +8 -1
- warp/tests/test_generics.py +15 -5
- warp/tests/test_grad.py +1 -1
- warp/tests/test_grad_customs.py +172 -12
- warp/tests/test_jax.py +254 -0
- warp/tests/test_large.py +29 -6
- warp/tests/test_launch.py +25 -0
- warp/tests/test_linear_solvers.py +20 -3
- warp/tests/test_matmul.py +61 -16
- warp/tests/test_matmul_lite.py +13 -13
- warp/tests/test_mempool.py +186 -0
- warp/tests/test_multigpu.py +3 -0
- warp/tests/test_options.py +16 -2
- warp/tests/test_peer.py +137 -0
- warp/tests/test_print.py +3 -1
- warp/tests/test_quat.py +23 -0
- warp/tests/test_sim_kinematics.py +97 -0
- warp/tests/test_snippet.py +126 -3
- warp/tests/test_streams.py +108 -79
- warp/tests/test_torch.py +16 -8
- warp/tests/test_utils.py +32 -27
- warp/tests/test_verify_fp.py +65 -0
- warp/tests/test_volume.py +1 -1
- warp/tests/unittest_serial.py +2 -0
- warp/tests/unittest_suites.py +12 -0
- warp/tests/unittest_utils.py +14 -7
- warp/thirdparty/unittest_parallel.py +15 -3
- warp/torch.py +10 -8
- warp/types.py +363 -246
- warp/utils.py +143 -19
- warp_lang-1.0.0.dist-info/LICENSE.md +126 -0
- warp_lang-1.0.0.dist-info/METADATA +394 -0
- {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/RECORD +167 -86
- warp/sim/optimizer.py +0 -138
- warp_lang-0.11.0.dist-info/LICENSE.md +0 -36
- warp_lang-0.11.0.dist-info/METADATA +0 -238
- /warp/tests/{walkthough_debug.py → walkthrough_debug.py} +0 -0
- {warp_lang-0.11.0.dist-info → warp_lang-1.0.0.dist-info}/WHEEL +0 -0
- {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()
|