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