Source code for pulse.cardiac_model

"""This module defines the cardiac model.

The cardiac model is a combination of a material model,
an active model, and a compressibility model.
"""

import logging
from dataclasses import dataclass, field
from typing import Protocol

import dolfinx
import ufl

from .viscoelasticity import NoneViscoElasticity

logger = logging.getLogger(__name__)


[docs] class ActiveModel(Protocol): def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
[docs] class Compressibility(Protocol): def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def is_compressible(self) -> bool: ...
[docs] def register(self, p: dolfinx.fem.Function | None) -> None: ...
[docs] class HyperElasticMaterial(Protocol): def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
[docs] class ViscoElasticity(Protocol): def strain_energy(self, C_dot: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def P(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ... def S(self, C_dot: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
[docs] @dataclass(frozen=True, slots=True) class CardiacModel: material: HyperElasticMaterial active: ActiveModel compressibility: Compressibility viscoelasticity: ViscoElasticity = field(default_factory=NoneViscoElasticity) def __post_init__(self): logger.debug("Created CardiacModel with components:") logger.debug(f" Material: {type(self.material).__name__}") logger.debug(f" Active Model: {type(self.active).__name__}") logger.debug(f" Compressibility: {type(self.compressibility).__name__}") logger.debug(f" Viscoelasticity: {type(self.viscoelasticity).__name__}") def strain_energy( self, C: ufl.core.expr.Expr, C_dot: ufl.core.expr.Expr | None = None, ) -> ufl.core.expr.Expr: psi = ( self.material.strain_energy(C) + self.active.strain_energy(C) + self.compressibility.strain_energy(C) ) if C_dot is not None: psi += self.viscoelasticity.strain_energy(C_dot) return psi
[docs] def S( self, C: ufl.core.expr.Expr, C_dot: ufl.core.expr.Expr | None = None, ) -> ufl.core.expr.Expr: """Cauchy stress for the cardiac model.""" S = self.material.S(C) + self.active.S(C) + self.compressibility.S(C) if C_dot is not None: S += self.viscoelasticity.S(C_dot) return S
[docs] def P( self, F: ufl.core.expr.Expr, F_dot: ufl.core.expr.Expr | None = None, ) -> ufl.core.expr.Expr: """First Piola-Kirchhoff stress for the cardiac model.""" P = self.material.P(F) + self.active.P(F) + self.compressibility.P(F) if F_dot is not None: P += self.viscoelasticity.P(F_dot) return P
[docs] def sigma( self, F: ufl.core.expr.Expr, F_dot: ufl.core.expr.Expr | None = None, ) -> ufl.core.expr.Expr: r"""Cauchy stress tensor Parameters ---------- F : ufl.core.expr.Expr The deformation gradient F_dot : ufl.core.expr.Expr | None Returns ------- ufl.core.expr.Expr The Cauchy stress tensor """ from .kinematics import InversePiolaTransform return InversePiolaTransform(self.P(F, F_dot), F)