Skip to content

Commit f7201b0

Browse files
committed
Add pre-stressing
1 parent 3d2a396 commit f7201b0

13 files changed

Lines changed: 355 additions & 15 deletions

.cspell_dict.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ allreduce
44
argsort
55
astype
66
atol
7+
Barnafi
78
Bartolucci
89
basix
910
bcast
@@ -17,9 +18,11 @@ cardiomyocytes
1718
cellname
1819
Chiara
1920
cofac
21+
cofnorm
2022
cpus
2123
Cristobal
2224
Culloch
25+
Davide
2326
deviatoric
2427
dirichletbc
2528
dofmap
@@ -40,6 +43,7 @@ Francesco
4043
functionspace
4144
geodir
4245
gradu
46+
Grice
4347
Guccione
4448
Holohan
4549
Holzapfel
@@ -90,6 +94,7 @@ Quarteroni
9094
regazzoni
9195
Regazzoni
9296
Remedios
97+
Riccobelli
9398
rique
9499
rtol
95100
scifem
@@ -99,10 +104,12 @@ Silvano
99104
Sorine
100105
Stefano
101106
subplus
107+
Taras
102108
tomek
103109
Tomek
104110
TRAME
105111
ureg
112+
Usyk
106113
varepsilon
107114
Venant
108115
Verlag

docs/refs.bib

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,13 @@ @article{land2017model
6767
year={2017},
6868
publisher={Elsevier}
6969
}
70+
71+
@article{barnafi2024reconstructing,
72+
title={Reconstructing relaxed configurations in elastic bodies: Mathematical formulations and numerical methods for cardiac modeling},
73+
author={Barnafi, NA and Regazzoni, Francesco and Riccobelli, Davide},
74+
journal={Computer Methods in Applied Mechanics and Engineering},
75+
volume={423},
76+
pages={116845},
77+
year={2024},
78+
publisher={Elsevier}
79+
}

src/pulse/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
kinematics,
2323
material_model,
2424
material_models,
25+
prestress,
2526
problem,
2627
units,
2728
utils,
@@ -30,15 +31,23 @@
3031
from .active_stress import ActiveStress
3132
from .boundary_conditions import BoundaryConditions, NeumannBC, RobinBC
3233
from .cardiac_model import CardiacModel
33-
from .compressibility import Compressibility, Compressible, Incompressible
34+
from .compressibility import (
35+
Compressibility,
36+
Compressible,
37+
Compressible2,
38+
Compressible3,
39+
Incompressible,
40+
)
3441
from .geometry import Geometry, HeartGeometry, Marker
3542
from .material_model import HyperElasticMaterial, Material
3643
from .material_models import (
3744
Guccione,
3845
HolzapfelOgden,
3946
NeoHookean,
4047
SaintVenantKirchhoff,
48+
Usyk,
4149
)
50+
from .prestress import PrestressProblem
4251
from .problem import BaseBC, DynamicProblem, StaticProblem
4352
from .units import Variable, ureg
4453
from .viscoelasticity import NoneViscoElasticity, ViscoElasticity, Viscous
@@ -87,4 +96,9 @@
8796
"ViscoElasticity",
8897
"Viscous",
8998
"__version__",
99+
"prestress",
100+
"PrestressProblem",
101+
"Usyk",
102+
"Compressible2",
103+
"Compressible3",
90104
]

src/pulse/active_model.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ def S(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
121121
"""
122122
return 2.0 * ufl.diff(self.strain_energy(F), F)
123123

124+
def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
125+
"""First Piola-Kirchhoff stress tensor for the active model.
126+
127+
Parameters
128+
----------
129+
F : ufl.core.expr.Expr
130+
The deformation gradient
131+
132+
Returns
133+
-------
134+
ufl.core.expr.Expr
135+
The first Piola-Kirchhoff stress tensor
136+
"""
137+
return ufl.diff(self.strain_energy(F), F)
138+
124139

125140
class Passive(ActiveModel):
126141
"""Active model with no active component.

src/pulse/boundary_conditions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class RobinBC:
3939
value: Variable
4040
marker: int
4141
damping: bool = False
42+
perpendicular: bool = False
4243

4344
def __post_init__(self):
4445
if not isinstance(self.value, Variable):

src/pulse/cardiac_model.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
1818

1919
def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
2020

21+
def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
22+
2123

2224
class Compressibility(Protocol):
2325
def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
2426

2527
def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
2628

29+
def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
30+
2731
def is_compressible(self) -> bool: ...
2832

2933
def register(self, p: dolfinx.fem.Function | None) -> None: ...
@@ -32,12 +36,16 @@ def register(self, p: dolfinx.fem.Function | None) -> None: ...
3236
class HyperElasticMaterial(Protocol):
3337
def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
3438

39+
def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
40+
3541
def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
3642

3743

3844
class ViscoElasticity(Protocol):
3945
def strain_energy(self, C_dot: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
4046

47+
def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
48+
4149
def S(self, C_dot: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
4250

4351

@@ -74,3 +82,14 @@ def S(
7482
if C_dot is not None:
7583
S += self.viscoelasticity.S(C_dot)
7684
return S
85+
86+
def P(
87+
self,
88+
F: ufl.core.expr.Expr,
89+
F_dot: ufl.core.expr.Expr | None = None,
90+
) -> ufl.core.expr.Expr:
91+
"""First Piola-Kirchhoff stress for the cardiac model."""
92+
P = self.material.P(F) + self.active.P(F) + self.compressibility.P(F)
93+
if F_dot is not None:
94+
P += self.viscoelasticity.P(F_dot)
95+
return P

src/pulse/compressibility.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
4242
"""Cauchy stress tensor for the compressibility model."""
4343
return 2.0 * ufl.diff(self.strain_energy(C), C)
4444

45+
def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
46+
"""First Piola-Kirchhoff stress tensor for the compressibility model."""
47+
C = F.T * F
48+
return ufl.diff(self.strain_energy(C), F)
49+
4550
@abc.abstractmethod
4651
def is_compressible(self) -> bool:
4752
"""Returns True if the material model is compressible."""
@@ -126,16 +131,38 @@ class Compressible2(Compressible):
126131
Strain energy density function is given by
127132
128133
.. math::
129-
\Psi = \kappa (J^2 - 1 - 2 \ln(J))
134+
\Psi = \kappa / 4 (J^2 - 1 - 2 \ln(J))
130135
131136
"""
132137

133138
kappa: Variable = field(default_factory=lambda: Variable(1e6, "Pa"))
134139

135140
def __str__(self) -> str:
136-
return "\u03ba (J ** 2 - 1 - 2 ln(J))"
141+
return "\u03ba / 4 (J ** 2 - 1 - 2 ln(J))"
137142

138143
def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
139144
J = ufl.sqrt(ufl.det(C))
140145
kappa = self.kappa.to_base_units()
141146
return 0.25 * kappa * (J**2 - 1 - 2 * ufl.ln(J))
147+
148+
149+
@dataclass(slots=True)
150+
class Compressible3(Compressible):
151+
r"""Compressible material model used in Usyk et al. 2002
152+
153+
Strain energy density function is given by
154+
155+
.. math::
156+
\Psi = \kappa / 2 (J - 1) * \ln(J)
157+
158+
"""
159+
160+
kappa: Variable = field(default_factory=lambda: Variable(5e4, "Pa"))
161+
162+
def __str__(self) -> str:
163+
return "\u03ba / 2 * (J - 1) * ln(J)"
164+
165+
def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
166+
J = ufl.sqrt(ufl.det(C))
167+
kappa = self.kappa.to_base_units()
168+
return 0.5 * kappa * (J - 1) * ufl.ln(J)

src/pulse/geometry.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ def _set_measures(self) -> None:
8181
metadata=self.metadata,
8282
)
8383

84+
def deform(self, u: dolfinx.fem.Function) -> None:
85+
"""Deform the geometry by a displacement field.
86+
Note that this will modify the mesh geometry in place.
87+
88+
Parameters
89+
----------
90+
u : dolfinx.fem.Function
91+
Displacement field to deform the geometry with.
92+
"""
93+
if not isinstance(u, dolfinx.fem.Function):
94+
raise TypeError("Displacement field must be a dolfinx.fem.Function")
95+
96+
import scifem
97+
98+
self.mesh.geometry.x[:] += scifem.evaluate_function(
99+
u,
100+
self.mesh.geometry.x,
101+
broadcast=False,
102+
)
103+
84104
@classmethod
85105
def from_cardiac_geometries(
86106
cls,

src/pulse/material_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
9494
.. math::
9595
\mathbf{P} = \frac{\partial \Psi}{\partial \mathbf{F}}
9696
"""
97-
F = ufl.variable(F)
98-
return ufl.diff(self.strain_energy(F), F)
97+
C = F.T * F
98+
return ufl.diff(self.strain_energy(C), F)
9999

100100
def sigma(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
101101
return kinematics.InversePiolaTransform(self.P(F), F)

src/pulse/material_models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
from .holzapfelogden import HolzapfelOgden
33
from .neo_hookean import NeoHookean
44
from .saint_venant_kirchhoff import SaintVenantKirchhoff
5+
from .usyk import Usyk
56

6-
__all__ = ["NeoHookean", "HolzapfelOgden", "SaintVenantKirchhoff", "Guccione"]
7+
__all__ = ["NeoHookean", "HolzapfelOgden", "SaintVenantKirchhoff", "Guccione", "Usyk"]

0 commit comments

Comments
 (0)