Skip to content

Commit 250829b

Browse files
committed
code cleanup
1 parent 735b4bb commit 250829b

File tree

4 files changed

+96
-69
lines changed

4 files changed

+96
-69
lines changed

genesis/engine/couplers/sap_coupler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -729,10 +729,10 @@ def compute_rigid_pcg_matrix_vector_product(self):
729729
)
730730

731731
@ti.func
732-
def compute_elastic_products(self, i_b, i_e, S, i_v, src):
732+
def compute_elastic_products(self, i_b, i_e, S, i_vs, src):
733733
p9 = ti.Vector.zero(gs.ti_float, 9)
734734
for i, j in ti.static(ti.ndrange(3, 4)):
735-
p9[i * 3 : i * 3 + 3] = p9[i * 3 : i * 3 + 3] + (S[j, i] * src[i_b, i_v[j]])
735+
p9[i * 3 : i * 3 + 3] = p9[i * 3 : i * 3 + 3] + (S[j, i] * src[i_b, i_vs[j]])
736736

737737
H9_p9 = ti.Vector.zero(gs.ti_float, 9)
738738

genesis/engine/solvers/fem_solver.py

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,21 @@ def compute_ele_hessian_gradient(self, f: ti.i32):
595595
hessian_field=self.elements_el_hessian,
596596
)
597597

598+
@ti.func
599+
def _func_compute_element_mapping_matrix(self, i_vs, B, i_b):
600+
"""
601+
Compute the element mapping matrix S for an element.
602+
"""
603+
S = ti.Matrix.zero(gs.ti_float, 4, 3)
604+
S[:3, :] = B
605+
S[3, :] = -B[0, :] - B[1, :] - B[2, :]
606+
607+
if ti.static(self._enable_vertex_constraints):
608+
for i in ti.static(range(4)):
609+
if self.vertex_constraints.is_constrained[i_vs[i], i_b]:
610+
S[i, :] = ti.Vector.zero(gs.ti_float, 3)
611+
return S
612+
598613
@ti.func
599614
def _func_compute_ele_energy(self, f: ti.i32):
600615
"""
@@ -622,21 +637,14 @@ def _func_compute_ele_energy(self, f: ti.i32):
622637
# add linearized damping energy
623638
if self._damping_beta > gs.EPS:
624639
damping_beta_over_dt = self._damping_beta / self._substep_dt
625-
i_v = self.elements_i[i_e].el2v
626-
S = ti.Matrix.zero(gs.ti_float, 4, 3)
640+
i_vs = self.elements_i[i_e].el2v
627641
B = self.elements_i[i_e].B
628-
S[:3, :] = B
629-
S[3, :] = -B[0, :] - B[1, :] - B[2, :]
630-
631-
if ti.static(self._enable_vertex_constraints):
632-
for i in ti.static(range(4)):
633-
if self.vertex_constraints.is_constrained[i_v[i], i_b]:
634-
S[i, :] = ti.Vector.zero(gs.ti_float, 3)
642+
S = self._func_compute_element_mapping_matrix(i_vs, B, i_b)
635643

636644
x_diff = ti.Vector.zero(gs.ti_float, 12)
637645
for i in ti.static(range(4)):
638646
x_diff[i * 3 : i * 3 + 3] = (
639-
self.elements_v[f + 1, i_v[i], i_b].pos - self.elements_v[f, i_v[i], i_b].pos
647+
self.elements_v[f + 1, i_vs[i], i_b].pos - self.elements_v[f, i_vs[i], i_b].pos
640648
)
641649
St_x_diff = ti.Vector.zero(gs.ti_float, 9)
642650
for i, j in ti.static(ti.ndrange(3, 4)):
@@ -657,17 +665,17 @@ def accumulate_vertex_force_preconditioner(self, f: ti.i32):
657665
damping_beta_over_dt = self._damping_beta / self._substep_dt
658666
damping_beta_factor = damping_beta_over_dt + 1.0
659667
# inertia
660-
for i_b, i_v in ti.ndrange(self._B, self.n_vertices):
668+
for i_b, i_vs in ti.ndrange(self._B, self.n_vertices):
661669
if not self.batch_active[i_b]:
662670
continue
663-
self.elements_v_energy[i_b, i_v].force = -self.elements_v_info[i_v].mass_over_dt2 * (
664-
(self.elements_v[f + 1, i_v, i_b].pos - self.elements_v_energy[i_b, i_v].inertia)
665-
+ (self.elements_v[f + 1, i_v, i_b].pos - self.elements_v[f, i_v, i_b].pos) * damping_alpha_dt
671+
self.elements_v_energy[i_b, i_vs].force = -self.elements_v_info[i_vs].mass_over_dt2 * (
672+
(self.elements_v[f + 1, i_vs, i_b].pos - self.elements_v_energy[i_b, i_vs].inertia)
673+
+ (self.elements_v[f + 1, i_vs, i_b].pos - self.elements_v[f, i_vs, i_b].pos) * damping_alpha_dt
666674
)
667-
self.pcg_state_v[i_b, i_v].diag3x3 = ti.Matrix.zero(gs.ti_float, 3, 3)
675+
self.pcg_state_v[i_b, i_vs].diag3x3 = ti.Matrix.zero(gs.ti_float, 3, 3)
668676
for i in ti.static(range(3)):
669-
self.pcg_state_v[i_b, i_v].diag3x3[i, i] = (
670-
self.elements_v_info[i_v].mass_over_dt2 * damping_alpha_factor
677+
self.pcg_state_v[i_b, i_vs].diag3x3[i, i] = (
678+
self.elements_v_info[i_vs].mass_over_dt2 * damping_alpha_factor
671679
)
672680

673681
# elastic
@@ -677,27 +685,19 @@ def accumulate_vertex_force_preconditioner(self, f: ti.i32):
677685
V = self.elements_i[i_e].V
678686
B = self.elements_i[i_e].B
679687
gradient = self.elements_el_energy[i_b, i_e].gradient
680-
i_v = self.elements_i[i_e].el2v
681-
S = ti.Matrix.zero(gs.ti_float, 4, 3)
682-
S[:3, :] = B
683-
S[3, :] = -B[0, :] - B[1, :] - B[2, :]
684-
685-
if ti.static(self._enable_vertex_constraints):
686-
for i in ti.static(range(4)):
687-
if self.vertex_constraints.is_constrained[i_v[i], i_b]:
688-
S[i, :] = ti.Vector.zero(gs.ti_float, 3)
689-
688+
i_vs = self.elements_i[i_e].el2v
689+
S = self._func_compute_element_mapping_matrix(i_vs, B, i_b)
690690
force = -V * gradient @ S.transpose()
691691

692692
# atomic
693693
for i in ti.static(range(4)):
694-
self.elements_v_energy[i_b, i_v[i]].force += force[:, i]
694+
self.elements_v_energy[i_b, i_vs[i]].force += force[:, i]
695695

696696
if self._damping_beta > gs.EPS:
697697
x_diff = ti.Vector.zero(gs.ti_float, 12)
698698
for i in ti.static(range(4)):
699699
x_diff[i * 3 : i * 3 + 3] = (
700-
self.elements_v[f + 1, i_v[i], i_b].pos - self.elements_v[f, i_v[i], i_b].pos
700+
self.elements_v[f + 1, i_vs[i], i_b].pos - self.elements_v[f, i_vs[i], i_b].pos
701701
)
702702
St_x_diff = ti.Vector.zero(gs.ti_float, 9)
703703
for i, j in ti.static(ti.ndrange(3, 4)):
@@ -712,22 +712,22 @@ def accumulate_vertex_force_preconditioner(self, f: ti.i32):
712712
for i, j in ti.static(ti.ndrange(4, 3)):
713713
S_H_St_x_diff[i * 3 : i * 3 + 3] += S[i, j] * H_St_x_diff[j * 3 : j * 3 + 3]
714714
for i in ti.static(range(4)):
715-
self.elements_v_energy[i_b, i_v[i]].force += (
715+
self.elements_v_energy[i_b, i_vs[i]].force += (
716716
-damping_beta_over_dt * V * S_H_St_x_diff[i * 3 : i * 3 + 3]
717717
)
718718

719719
# diagonal 3-by-3 block of hessian
720720
for k, i, j in ti.ndrange(4, 3, 3):
721-
self.pcg_state_v[i_b, i_v[k]].diag3x3 += (
721+
self.pcg_state_v[i_b, i_vs[k]].diag3x3 += (
722722
V * damping_beta_factor * S[k, i] * S[k, j] * self.elements_el_hessian[i_b, i, j, i_e]
723723
)
724724

725725
# inverse
726-
for i_b, i_v in ti.ndrange(self._B, self.n_vertices):
726+
for i_b, i_vs in ti.ndrange(self._B, self.n_vertices):
727727
if not self.batch_active[i_b]:
728728
continue
729729
# Use 3-by-3 diagonal block inverse for preconditioner
730-
self.pcg_state_v[i_b, i_v].prec = self.pcg_state_v[i_b, i_v].diag3x3.inverse()
730+
self.pcg_state_v[i_b, i_vs].prec = self.pcg_state_v[i_b, i_vs].diag3x3.inverse()
731731

732732
# Other options for preconditioner:
733733
# Uncomment one of the following lines to test different preconditioners
@@ -745,32 +745,25 @@ def compute_Ap(self):
745745
damping_alpha_factor = damping_alpha_dt + 1.0
746746
damping_beta_over_dt = self._damping_beta / self._substep_dt
747747
damping_beta_factor = damping_beta_over_dt + 1.0
748-
for i_b, i_v in ti.ndrange(self._B, self.n_vertices):
748+
for i_b, i_vs in ti.ndrange(self._B, self.n_vertices):
749749
if not self.batch_pcg_active[i_b]:
750750
continue
751-
self.pcg_state_v[i_b, i_v].Ap = (
752-
self.elements_v_info[i_v].mass_over_dt2 * damping_alpha_factor * self.pcg_state_v[i_b, i_v].p
751+
self.pcg_state_v[i_b, i_vs].Ap = (
752+
self.elements_v_info[i_vs].mass_over_dt2 * damping_alpha_factor * self.pcg_state_v[i_b, i_vs].p
753753
)
754754

755755
for i_b, i_e in ti.ndrange(self._B, self.n_elements):
756756
if not self.batch_pcg_active[i_b]:
757757
continue
758758
V = self.elements_i[i_e].V
759759
B = self.elements_i[i_e].B
760-
i_v = self.elements_i[i_e].el2v
761-
S = ti.Matrix.zero(gs.ti_float, 4, 3)
762-
S[:3, :] = B
763-
S[3, :] = -B[0, :] - B[1, :] - B[2, :]
764-
765-
if ti.static(self._enable_vertex_constraints):
766-
for i in ti.static(range(4)):
767-
if self.vertex_constraints.is_constrained[i_v[i], i_b]:
768-
S[i, :] = ti.Vector.zero(gs.ti_float, 3)
760+
i_vs = self.elements_i[i_e].el2v
761+
S = self._func_compute_element_mapping_matrix(i_vs, B, i_b)
769762

770763
p9 = ti.Vector([0.0] * 9, dt=gs.ti_float)
771764

772765
for i, j in ti.static(ti.ndrange(3, 4)):
773-
p9[i * 3 : i * 3 + 3] = p9[i * 3 : i * 3 + 3] + S[j, i] * self.pcg_state_v[i_b, i_v[j]].p
766+
p9[i * 3 : i * 3 + 3] = p9[i * 3 : i * 3 + 3] + S[j, i] * self.pcg_state_v[i_b, i_vs[j]].p
774767

775768
new_p9 = ti.Vector([0.0] * 9, dt=gs.ti_float)
776769

@@ -781,7 +774,7 @@ def compute_Ap(self):
781774

782775
# atomic
783776
for i in ti.static(range(4)):
784-
self.pcg_state_v[i_b, i_v[i]].Ap += (
777+
self.pcg_state_v[i_b, i_vs[i]].Ap += (
785778
(S[i, 0] * new_p9[0:3] + S[i, 1] * new_p9[3:6] + S[i, 2] * new_p9[6:9]) * V * damping_beta_factor
786779
)
787780

genesis/options/solvers.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ class SAPCouplerOptions(BaseCouplerOptions):
114114
"""
115115
Options configuring the inter-solver coupling for the Semi-Analytic Primal (SAP) contact solver used in Drake.
116116
117+
Note
118+
----
119+
Paper reference: https://arxiv.org/abs/2110.10107
120+
Drake reference: https://drake.mit.edu/release_notes/v1.5.0.html
121+
117122
Parameters
118123
----------
119124
n_sap_iterations : int, optional
@@ -152,10 +157,6 @@ class SAPCouplerOptions(BaseCouplerOptions):
152157
Type of contact against the floor for rigid bodies. Defaults to "vert". Can be "vert" or "none".
153158
enable_rigid_fem_contact : bool, optional
154159
Whether to enable coupling between rigid and FEM solvers. Defaults to True.
155-
Note
156-
----
157-
Paper reference: https://arxiv.org/abs/2110.10107
158-
Drake reference: https://drake.mit.edu/release_notes/v1.5.0.html
159160
"""
160161

161162
n_sap_iterations: int = 5
@@ -581,6 +582,13 @@ class FEMOptions(Options):
581582
"""
582583
Options configuring the FEMSolver.
583584
585+
Note
586+
----
587+
- Damping coefficients are used to control the damping effect in the simulation.
588+
They are used in the Rayleigh Damping model, which is a common damping model in FEM simulations.
589+
Reference: https://doc.comsol.com/5.5/doc/com.comsol.help.sme/sme_ug_modeling.05.083.html
590+
- TODO Move it to material parameters in the future instead of solver options.
591+
584592
Parameters
585593
----------
586594
dt : float, optional
@@ -614,12 +622,6 @@ class FEMOptions(Options):
614622
Rayleigh Damping factor for the implicit solver. Defaults to 5e-4. Only used when `use_implicit_solver` is True.
615623
enable_vertex_constraints : bool, optional
616624
Whether to enable vertex constraints. Defaults to False.
617-
Note
618-
----
619-
- Damping coefficients are used to control the damping effect in the simulation.
620-
They are used in the Rayleigh Damping model, which is a common damping model in FEM simulations.
621-
Reference: https://doc.comsol.com/5.5/doc/com.comsol.help.sme/sme_ug_modeling.05.083.html
622-
- TODO Move it to material parameters in the future instead of solver options.
623625
"""
624626

625627
dt: Optional[float] = None

tests/test_fem.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,8 @@ def test_fem_articulated(fem_material_linear_corotated_soft, show_viewer):
586586

587587
def test_implicit_hard_vertex_constraint(fem_material_linear_corotated, show_viewer):
588588
"""
589-
Test if a box with hard vertex constraints has those vertices fixed,
590-
and that updating and removing constraints works correctly.
589+
Test if a box with hard vertex constraints has those vertices fixed, and that updating and removing constraints
590+
works correctly.
591591
"""
592592
scene = gs.Scene(
593593
sim_options=gs.options.SimOptions(
@@ -628,7 +628,7 @@ def test_implicit_hard_vertex_constraint(fem_material_linear_corotated, show_vie
628628

629629
positions = cube.get_state().pos[0][verts_idx]
630630
assert_allclose(
631-
positions, initial_target_poss, tol=0.0
631+
positions, initial_target_poss, tol=gs.EPS
632632
), "Vertices should stay at initial target positions with hard constraints"
633633
new_target_poss = initial_target_poss + gs.tensor(
634634
[[0.1, 0.1, 0.1]],
@@ -642,7 +642,7 @@ def test_implicit_hard_vertex_constraint(fem_material_linear_corotated, show_vie
642642

643643
positions_after_update = cube.get_state().pos[0][verts_idx]
644644
assert_allclose(
645-
positions_after_update, new_target_poss, tol=0.0
645+
positions_after_update, new_target_poss, tol=gs.EPS
646646
), "Vertices should be at new target positions after updating constraints"
647647

648648
cube.remove_vertex_constraints()
@@ -653,15 +653,30 @@ def test_implicit_hard_vertex_constraint(fem_material_linear_corotated, show_vie
653653
scene.step()
654654

655655
state = cube.get_state()
656-
min_pos_z = state.pos[..., 2].min()
656+
center = state.pos.mean(axis=(0, 1))
657+
assert_allclose(
658+
center,
659+
np.array([0.2, 0.13, 0.1], dtype=np.float32),
660+
atol=0.2,
661+
err_msg=f"Cube center {center} moves too far from [0.2, 0.13, 0.1] after removing constraints.",
662+
)
663+
664+
velocity = state.vel.mean(axis=(0, 1))
665+
assert_allclose(
666+
velocity,
667+
np.array([0.0, 0.0, 0.0], dtype=np.float32),
668+
atol=1e-5,
669+
err_msg=f"Cube velocity {velocity} should be close to zero after settling.",
670+
)
671+
657672
# The contact requires some penetration to generate enough contact force to cancel out gravity
673+
min_pos_z = state.pos[..., 2].min()
658674
assert_allclose(min_pos_z, -2.0e-5, atol=5e-6), f"Cube minimum Z position {min_pos_z} is not close to -2.0e-5."
659675

660676

661677
def test_sphere_box_vertex_constraint(fem_material_linear_corotated, show_viewer):
662678
"""
663-
Test if a box with hard vertex constraints has those vertices fixed,
664-
and that updating and removing constraints works correctly.
679+
Test if a box with hard vertex constraints has those vertices fixed, and collisiong with a sphere works correctly.
665680
"""
666681
scene = gs.Scene(
667682
sim_options=gs.options.SimOptions(
@@ -699,7 +714,6 @@ def test_sphere_box_vertex_constraint(fem_material_linear_corotated, show_viewer
699714
)
700715

701716
scene.build()
702-
# return
703717
if show_viewer:
704718
sphere_debug = scene.draw_debug_spheres(poss=initial_target_poss, radius=0.02, color=(1, 0, 1, 0.8))
705719

@@ -711,10 +725,28 @@ def test_sphere_box_vertex_constraint(fem_material_linear_corotated, show_viewer
711725
pos = cube.get_state().pos
712726
fixed_pos = pos[0][verts_idx]
713727
assert_allclose(
714-
fixed_pos, initial_target_poss, tol=0.0
728+
fixed_pos, initial_target_poss, tol=gs.EPS
715729
), "Vertices should stay at initial target positions with hard constraints"
716730

717-
min_sphere_pos_z = sphere.get_state().pos[..., 2].min()
731+
state = sphere.get_state()
732+
center = state.pos.mean(axis=(0, 1))
733+
assert_allclose(
734+
center,
735+
np.array([0.4, 0.4, 0.1], dtype=np.float32),
736+
atol=0.2,
737+
err_msg=f"Sphere center {center} moved too much from initial position [0.4, 0.4, 0.1].",
738+
)
739+
740+
# Using a larger tolerance here since the sphere is rolling, rolling friction is not accurately modeled.
741+
velocity = state.vel.mean(axis=(0, 1))
742+
assert_allclose(
743+
velocity,
744+
np.array([0.0, 0.0, 0.0], dtype=np.float32),
745+
atol=0.03,
746+
err_msg=f"Sphere velocity {velocity} should be close to zero after settling.",
747+
)
748+
749+
min_sphere_pos_z = state.pos[..., 2].min()
718750
assert_allclose(
719751
min_sphere_pos_z, -1e-3, atol=2e-4
720752
), f"Sphere minimum Z position {min_sphere_pos_z} is not close to cube bottom surface."

0 commit comments

Comments
 (0)