Skip to content

Commit 18a4761

Browse files
authored
[FEATURE] Add linear_corotated elastic material for fem (#1304)
1 parent c3c47c7 commit 18a4761

File tree

4 files changed

+263
-15
lines changed

4 files changed

+263
-15
lines changed

genesis/engine/materials/FEM/base.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ def __init__(
5757
# will be set when added to solver
5858
self._idx = None
5959

60+
def build(self, fem_solver):
61+
pass
62+
63+
@ti.func
64+
def pre_compute(self, J, F, i_e, i_b):
65+
pass
66+
6067
@ti.func
6168
def update_stress(self, mu, lam, J, F, actu, m_dir):
6269
raise NotImplementedError
@@ -66,7 +73,7 @@ def compute_energy_gradient_hessian(self, mu, lam, J, F, actu, m_dir, i_e, i_b,
6673
raise NotImplementedError
6774

6875
@ti.func
69-
def compute_energy(self, mu, lam, J, F, actu, m_dir):
76+
def compute_energy(self, mu, lam, J, F, actu, m_dir, i_e, i_b):
7077
raise NotImplementedError
7178

7279
@property

genesis/engine/materials/FEM/elastic.py

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Elastic(Base):
3535
Constitutive model to use for stress computation. Options are:
3636
- 'linear': Linear elasticity model
3737
- 'stable_neohookean': A numerically stable Neo-Hookean model
38+
- 'linear_corotated': Linear corotated elasticity model
3839
Default is 'linear'.
3940
"""
4041

@@ -63,11 +64,28 @@ def __init__(
6364
self.hessian_invariant = False
6465
if model == "stable_neohooken":
6566
gs.logger.warning("The 'stable_neohooken' model is deprecated. Use 'stable_neohookean' instead.")
67+
elif model == "linear_corotated":
68+
self.build = self.build_linear_corotated
69+
self.pre_compute = self.pre_compute_linear_corotated
70+
self.update_stress = self.update_stress_linear_corotated
71+
self.compute_energy_gradient_hessian = self.compute_energy_gradient_hessian_linear_corotated
72+
self.compute_energy_gradient = self.compute_energy_gradient_linear_corotated
73+
self.compute_energy = self.compute_energy_linear_corotated
74+
self.hessian_static = False
6675
else:
6776
gs.raise_exception(f"Unrecognized constitutive model: {model}")
6877

6978
self._model = model
7079

80+
def build_linear_corotated(self, fem_solver):
81+
self.R = ti.field(dtype=gs.ti_mat3, shape=(fem_solver._B, fem_solver.n_elements))
82+
83+
@ti.func
84+
def pre_compute_linear_corotated(self, J, F, i_e, i_b):
85+
U, S, V = ti.svd(F)
86+
R = U @ V.transpose()
87+
self.R[i_b, i_e] = R
88+
7189
@ti.func
7290
def update_stress_linear(self, mu, lam, J, F, actu, m_dir):
7391
I = ti.Matrix.identity(dt=gs.ti_float, n=3)
@@ -87,6 +105,10 @@ def update_stress_stable_neohookean(self, mu, lam, J, F, actu, m_dir):
87105

88106
return stress
89107

108+
@ti.func
109+
def update_stress_linear_corotated(self, mu, lam, J, F, actu, m_dir):
110+
raise NotImplementedError("Linear corotated stress update is not implemented yet.")
111+
90112
@ti.func
91113
def compute_energy_gradient_hessian_linear(self, mu, lam, J, F, actu, m_dir, i_e, i_b, hessian_field):
92114
"""
@@ -197,7 +219,7 @@ def compute_energy_gradient_linear(self, mu, lam, J, F, actu, m_dir, i_e, i_b):
197219
return energy, gradient
198220

199221
@ti.func
200-
def compute_energy_linear(self, mu, lam, J, F, actu, m_dir):
222+
def compute_energy_linear(self, mu, lam, J, F, actu, m_dir, i_e, i_b):
201223
"""
202224
Compute the energy for linear elasticity.
203225
@@ -316,7 +338,7 @@ def compute_energy_gradient_stable_neohookean(self, mu, lam, J, F, actu, m_dir,
316338
raise NotImplementedError("Gradient computation is not implemented for stable_neohookean model.")
317339

318340
@ti.func
319-
def compute_energy_stable_neohookean(self, mu, lam, J, F, actu, m_dir):
341+
def compute_energy_stable_neohookean(self, mu, lam, J, F, actu, m_dir, i_e, i_b):
320342
"""
321343
Compute the energy for the stable Neo-Hookean model.
322344
@@ -354,6 +376,153 @@ def compute_energy_stable_neohookean(self, mu, lam, J, F, actu, m_dir):
354376

355377
return energy
356378

379+
@ti.func
380+
def compute_energy_gradient_hessian_linear_corotated(self, mu, lam, J, F, actu, m_dir, i_e, i_b, hessian_field):
381+
"""
382+
Compute the energy, gradient, and Hessian for linear elasticity.
383+
384+
Parameters
385+
----------
386+
mu: float
387+
The first Lame parameter (shear modulus).
388+
lam: float
389+
The second Lame parameter (related to volume change).
390+
J: float
391+
The determinant of the deformation gradient F.
392+
F: ti.Matrix
393+
The deformation gradient matrix.
394+
actu: ti.Matrix
395+
The activation matrix (not used in linear elasticity).
396+
m_dir: ti.Matrix
397+
The material direction (not used in linear elasticity).
398+
hessian_field: ti.Matrix
399+
The Hessian of the energy with respect to the deformation gradient F.
400+
401+
Returns
402+
-------
403+
energy: float
404+
The computed energy.
405+
gradient: ti.Matrix
406+
The gradient of the energy with respect to the deformation gradient F.
407+
408+
Notes
409+
-------
410+
This implementation assumes small deformations and linear stress-strain relationship.
411+
It is adapted from the HOBAKv1 implementation for linear elasticity:
412+
https://github.com/theodorekim/HOBAKv1/blob/main/src/Hyperelastic/Volume/LINEAR.cpp
413+
414+
"""
415+
R = self.R[i_b, i_e]
416+
F_hat = R.transpose() @ F
417+
# E = 1/2(F_hat + F_hat.transpose()) - I
418+
eps = 0.5 * (F_hat + F_hat.transpose())
419+
for i in ti.static(range(3)):
420+
eps[i, i] -= 1.0
421+
trEps = eps.trace()
422+
energy = mu * eps.norm_sqr() + 0.5 * lam * trEps**2
423+
424+
gradient = 2.0 * mu * R @ eps + lam * trEps * R
425+
426+
# Zero out the matrix
427+
for i in ti.static(ti.grouped(ti.ndrange(3, 3))):
428+
hessian_field[i_b, i, i_e].fill(0.0)
429+
430+
# Identity part
431+
for i, k in ti.static(ti.ndrange(3, 3)):
432+
hessian_field[i_b, i, i, i_e][k, k] = mu
433+
434+
for i, j, alpha, beta in ti.static(ti.ndrange(3, 3, 3, 3)):
435+
hessian_field[i_b, j, beta, i_e][i, alpha] += mu * R[i, beta] * R[alpha, j] + lam * R[alpha, beta] * R[i, j]
436+
437+
return energy, gradient
438+
439+
@ti.func
440+
def compute_energy_gradient_linear_corotated(self, mu, lam, J, F, actu, m_dir, i_e, i_b):
441+
"""
442+
Compute the energy, gradient for linear elasticity.
443+
444+
Parameters
445+
----------
446+
mu: float
447+
The first Lame parameter (shear modulus).
448+
lam: float
449+
The second Lame parameter (related to volume change).
450+
J: float
451+
The determinant of the deformation gradient F.
452+
F: ti.Matrix
453+
The deformation gradient matrix.
454+
actu: ti.Matrix
455+
The activation matrix (not used in linear elasticity).
456+
m_dir: ti.Matrix
457+
The material direction (not used in linear elasticity).
458+
459+
Returns
460+
-------
461+
energy: float
462+
The computed energy.
463+
gradient: ti.Matrix
464+
The gradient of the energy with respect to the deformation gradient F.
465+
466+
Notes
467+
-------
468+
This implementation assumes small deformations and linear stress-strain relationship.
469+
It is adapted from the HOBAKv1 implementation for linear elasticity:
470+
https://github.com/theodorekim/HOBAKv1/blob/main/src/Hyperelastic/Volume/LINEAR.cpp
471+
472+
"""
473+
F_hat = self.R[i_b, i_e].transpose() @ F
474+
# E = 1/2(F_hat + F_hat.transpose()) - I
475+
eps = 0.5 * (F_hat + F_hat.transpose())
476+
for i in ti.static(range(3)):
477+
eps[i, i] -= 1.0
478+
trEps = eps.trace()
479+
energy = mu * eps.norm_sqr() + 0.5 * lam * trEps**2
480+
gradient = 2.0 * mu * self.R[i_b, i_e] @ eps + lam * trEps * self.R[i_b, i_e]
481+
482+
return energy, gradient
483+
484+
@ti.func
485+
def compute_energy_linear_corotated(self, mu, lam, J, F, actu, m_dir, i_e, i_b):
486+
"""
487+
Compute the energy for linear elasticity.
488+
489+
Parameters
490+
----------
491+
mu: float
492+
The first Lame parameter (shear modulus).
493+
lam: float
494+
The second Lame parameter (related to volume change).
495+
J: float
496+
The determinant of the deformation gradient F.
497+
F: ti.Matrix
498+
The deformation gradient matrix.
499+
actu: ti.Matrix
500+
The activation matrix (not used in linear elasticity).
501+
m_dir: ti.Matrix
502+
The material direction (not used in linear elasticity).
503+
504+
Returns
505+
-------
506+
energy: float
507+
The computed energy.
508+
509+
Notes
510+
-------
511+
This implementation assumes small deformations and linear stress-strain relationship.
512+
It is adapted from the HOBAKv1 implementation for linear elasticity:
513+
https://github.com/theodorekim/HOBAKv1/blob/main/src/Hyperelastic/Volume/LINEAR.cpp
514+
515+
"""
516+
F_hat = self.R[i_b, i_e].transpose() @ F
517+
# E = 1/2(F_hat + F_hat.transpose()) - I
518+
eps = 0.5 * (F_hat + F_hat.transpose())
519+
for i in ti.static(range(3)):
520+
eps[i, i] -= 1.0
521+
trEps = eps.trace()
522+
energy = mu * eps.norm_sqr() + 0.5 * lam * trEps**2
523+
524+
return energy
525+
357526
@property
358527
def model(self):
359528
"""The name of the constitutive model ('linear' or 'stable_neohookean')."""

genesis/engine/solvers/fem_solver.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ def build(self):
293293
for entity in self._entities:
294294
entity._add_to_solver()
295295

296+
for mat in self._mats:
297+
mat.build(self)
298+
296299
def add_entity(self, idx, material, morph, surface):
297300
# add material's update methods if not matching any existing material
298301
exist = False
@@ -396,6 +399,18 @@ def compute_pos(self, f: ti.i32):
396399
dt * self.elements_v[f + 1, i_v, i_b].vel + self.elements_v[f, i_v, i_b].pos
397400
)
398401

402+
@ti.kernel
403+
def precompute_material_data(self, f: ti.i32):
404+
for i_b, i_e in ti.ndrange(self._B, self.n_elements):
405+
if not self.batch_active[i_b]:
406+
continue
407+
408+
J, F = self._compute_ele_J_F(f, i_e, i_b) # use last time step's pos to compute
409+
410+
for mat_idx in ti.static(self._mats_idx):
411+
if self.elements_i[i_e].mat_idx == mat_idx:
412+
self._mats[mat_idx].pre_compute(J=J, F=F, i_e=i_e, i_b=i_b)
413+
399414
@ti.kernel
400415
def init_pos_and_inertia(self, f: ti.i32):
401416
dt = self.substep_dt
@@ -411,10 +426,10 @@ def _compute_ele_J_F(self, f: ti.i32, i_e: ti.i32, i_b: ti.i32):
411426
Compute the determinant (J) and deformation gradient (F) for an element.
412427
"""
413428
i_v0, i_v1, i_v2, i_v3 = self.elements_i[i_e].el2v
414-
pos_v0 = self.elements_v[f + 1, i_v0, i_b].pos
415-
pos_v1 = self.elements_v[f + 1, i_v1, i_b].pos
416-
pos_v2 = self.elements_v[f + 1, i_v2, i_b].pos
417-
pos_v3 = self.elements_v[f + 1, i_v3, i_b].pos
429+
pos_v0 = self.elements_v[f, i_v0, i_b].pos
430+
pos_v1 = self.elements_v[f, i_v1, i_b].pos
431+
pos_v2 = self.elements_v[f, i_v2, i_b].pos
432+
pos_v3 = self.elements_v[f, i_v3, i_b].pos
418433
D = ti.Matrix.cols([pos_v0 - pos_v3, pos_v1 - pos_v3, pos_v2 - pos_v3])
419434

420435
B = self.elements_i[i_e].B
@@ -429,7 +444,7 @@ def compute_ele_hessian_gradient(self, f: ti.i32):
429444
if not self.batch_active[i_b]:
430445
continue
431446

432-
J, F = self._compute_ele_J_F(f, i_e, i_b)
447+
J, F = self._compute_ele_J_F(f + 1, i_e, i_b)
433448

434449
for mat_idx in ti.static(self._mats_idx):
435450
if self.elements_i[i_e].mat_idx == mat_idx:
@@ -472,7 +487,7 @@ def _func_compute_ele_energy(self, f: ti.i32):
472487
if not self.batch_linesearch_active[i_b]:
473488
continue
474489

475-
J, F = self._compute_ele_J_F(f, i_e, i_b)
490+
J, F = self._compute_ele_J_F(f + 1, i_e, i_b)
476491

477492
for mat_idx in ti.static(self._mats_idx):
478493
if self.elements_i[i_e].mat_idx == mat_idx:
@@ -483,6 +498,8 @@ def _func_compute_ele_energy(self, f: ti.i32):
483498
F=F,
484499
actu=self.elements_el[f, i_e, i_b].actu,
485500
m_dir=self.elements_i[i_e].muscle_direction,
501+
i_e=i_e,
502+
i_b=i_b,
486503
)
487504

488505
# add linearized damping energy
@@ -851,6 +868,7 @@ def process_input_grad(self):
851868
def substep_pre_coupling(self, f):
852869
if self.is_active():
853870
if self._use_implicit_solver:
871+
self.precompute_material_data(f)
854872
self.init_pos_and_inertia(f)
855873
self.batch_solve(f)
856874
self.setup_pos_vel(f)

0 commit comments

Comments
 (0)