Klotz curve

Klotz curve#

Inlate a geometry to a pressure using different material models, and compare with the Klotz curve.

import math
import matplotlib.pyplot as plt
import numpy as np
import pulse
try:
    from dolfin_adjoint import Constant, DirichletBC
except ImportError:
    from dolfin import Constant, DirichletBC
ED_pressure = 1.6  # kPa
def setup_material(material_model):
    """
    Choose parameters based on

    Hadjicharalambous, Myrianthi, et al. "Analysis of passive
    cardiac constitutive laws for parameter estimation using 3D
    tagged MRI." Biomechanics and modeling in mechanobiology 14.4
    (2015): 807-828.

    These parameters did not really match the Klotz curve here.
    Perhaps they did some more tuning?

    """
    if material_model == "guccione":
        matparams = pulse.Guccione.default_parameters()
        matparams["C"] = 0.18  # kPa
        matparams["bf"] = 27.75
        matparams["bt"] = 5.37
        matparams["bfs"] = 2.445
        material = pulse.Guccione(
            parameters=matparams,
            f0=geometry.f0,
            s0=geometry.s0,
            n0=geometry.n0,
        )

    elif material_model == "neo_hookean":
        matparams = pulse.NeoHookean.default_parameters()
        matparams["mu"] = 10.0  # kPa
        material = pulse.NeoHookean(parameters=matparams)

    elif material_model == "holzapfel_ogden":
        matparams = pulse.HolzapfelOgden.default_parameters()

        matparams["a"] = 4.0  # kPa
        matparams["a_f"] = 10.0  # kPa
        matparams["b"] = 5.0
        matparams["b_f"] = 5.0
        material = pulse.HolzapfelOgden(
            parameters=matparams,
            f0=geometry.f0,
            s0=geometry.s0,
            n0=geometry.n0,
        )
    return material
def klotz_curve(ED_pressure):
    """

    EDPVR based on Klotz curve

    Klotz, Stefan, et al. "Single-beat estimation of end-diastolic
    pressure-volume relationship: a novel method with potential for
    noninvasive application." American Journal of Physiology-Heart and
    Circulatory Physiology 291.1 (2006): H403-H412.

    """

    # Some point at the EDPVR line
    Vm = 148.663
    Pm = ED_pressure

    # Some constants
    An = 27.8
    Bn = 2.76

    # kpa to mmhg
    Pm = Pm * 760 / 101.325

    V0 = Vm * (0.6 - 0.006 * Pm)
    V30 = V0 + (Vm - V0) / (Pm / An) ** (1.0 / Bn)

    beta = math.log(Pm / 30.0) / math.log(Vm / V30)
    alpha = 30.0 / V30**beta

    # Unloaded volume (not used here)
    # P_V0 = alpha * V0 ** beta

    vs = [V0]
    ps = [0.0]
    for p in np.linspace(1.0, 12.0):
        vi = (p / alpha) ** (1.0 / beta)
        vs.append(vi)
        ps.append(p * 101.325 / 760)  # Convert from mmhg to kPa

    return vs, ps
def fix_basal_plane(W):
    V = W if W.sub(0).num_sub_spaces() == 0 else W.sub(0)
    bc = DirichletBC(
        V,
        Constant((0.0, 0.0, 0.0)),
        geometry.ffun,
        geometry.markers["BASE"][0],
    )
    return bc
geometry = pulse.HeartGeometry.from_file(pulse.mesh_paths["simple_ellipsoid"])
geometry.mesh.coordinates()[:] *= 4.7
# geometry = pulse.geometries.prolate_ellipsoid_geometry(mesh_size_factor=3.0)
# geometry.mesh.coordinates()[:] *= 0.37
2025-06-06 10:58:51,933 [418] INFO     pulse.geometry_utils: 
Load mesh from h5
dirichlet_bc = [fix_basal_plane]
lvp = Constant(0.0)
lv_marker = geometry.markers["ENDO"][0]
lv_pressure = pulse.NeumannBC(traction=lvp, marker=lv_marker, name="lv")
neumann_bc = [lv_pressure]
bcs = pulse.BoundaryConditions(dirichlet=dirichlet_bc, neumann=neumann_bc)
fig, ax = plt.subplots()
for material_model in ["neo_hookean", "guccione", "holzapfel_ogden"]:
    material = setup_material(material_model)
    problem = pulse.MechanicsProblem(geometry, material, bcs)

    pressures = [0.0]
    volumes = [geometry.cavity_volume()]

    for p in np.linspace(0, ED_pressure, 10)[1:]:
        pulse.iterate.iterate(problem, lvp, p)

        pressures.append(p)
        volumes.append(geometry.cavity_volume(u=problem.state.split()[0]))

    ax.plot(volumes, pressures, label=" ".join(material_model.split("_")))

    # Reset pressure
    lvp.assign(Constant(0.0))
2025-06-06 10:58:52,063 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:58:52,064 [418] INFO     pulse.iterate: Current control: f_20 = 0.000
2025-06-06 10:58:52,065 [418] INFO     pulse.iterate: Target: 0.178
2025-06-06 10:58:54,230 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:58:54,230 [418] INFO     pulse.iterate: Current control: f_20 = 0.178
2025-06-06 10:58:54,231 [418] INFO     pulse.iterate: Target: 0.356
2025-06-06 10:58:56,083 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:58:56,084 [418] INFO     pulse.iterate: Current control: f_20 = 0.356
2025-06-06 10:58:56,085 [418] INFO     pulse.iterate: Target: 0.533
2025-06-06 10:58:57,936 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:58:57,937 [418] INFO     pulse.iterate: Current control: f_20 = 0.533
2025-06-06 10:58:57,937 [418] INFO     pulse.iterate: Target: 0.711
2025-06-06 10:58:59,790 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:58:59,790 [418] INFO     pulse.iterate: Current control: f_20 = 0.711
2025-06-06 10:58:59,791 [418] INFO     pulse.iterate: Target: 0.889
2025-06-06 10:59:01,640 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:01,641 [418] INFO     pulse.iterate: Current control: f_20 = 0.889
2025-06-06 10:59:01,642 [418] INFO     pulse.iterate: Target: 1.067
2025-06-06 10:59:03,494 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:03,495 [418] INFO     pulse.iterate: Current control: f_20 = 1.067
2025-06-06 10:59:03,496 [418] INFO     pulse.iterate: Target: 1.244
2025-06-06 10:59:05,350 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:05,351 [418] INFO     pulse.iterate: Current control: f_20 = 1.244
2025-06-06 10:59:05,352 [418] INFO     pulse.iterate: Target: 1.422
2025-06-06 10:59:07,254 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:07,255 [418] INFO     pulse.iterate: Current control: f_20 = 1.422
2025-06-06 10:59:07,255 [418] INFO     pulse.iterate: Target: 1.600
2025-06-06 10:59:09,179 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:09,180 [418] INFO     pulse.iterate: Current control: f_20 = 0.000
2025-06-06 10:59:09,180 [418] INFO     pulse.iterate: Target: 0.178
2025-06-06 10:59:15,202 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:15,202 [418] INFO     pulse.iterate: Current control: f_20 = 0.178
2025-06-06 10:59:15,203 [418] INFO     pulse.iterate: Target: 0.356
2025-06-06 10:59:20,260 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:20,261 [418] INFO     pulse.iterate: Current control: f_20 = 0.356
2025-06-06 10:59:20,261 [418] INFO     pulse.iterate: Target: 0.533
2025-06-06 10:59:25,314 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:25,315 [418] INFO     pulse.iterate: Current control: f_20 = 0.533
2025-06-06 10:59:25,315 [418] INFO     pulse.iterate: Target: 0.711
2025-06-06 10:59:29,870 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:29,870 [418] INFO     pulse.iterate: Current control: f_20 = 0.711
2025-06-06 10:59:29,871 [418] INFO     pulse.iterate: Target: 0.889
2025-06-06 10:59:34,421 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:34,421 [418] INFO     pulse.iterate: Current control: f_20 = 0.889
2025-06-06 10:59:34,422 [418] INFO     pulse.iterate: Target: 1.067
2025-06-06 10:59:38,480 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:38,481 [418] INFO     pulse.iterate: Current control: f_20 = 1.067
2025-06-06 10:59:38,481 [418] INFO     pulse.iterate: Target: 1.244
2025-06-06 10:59:42,536 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:42,537 [418] INFO     pulse.iterate: Current control: f_20 = 1.244
2025-06-06 10:59:42,538 [418] INFO     pulse.iterate: Target: 1.422
2025-06-06 10:59:46,616 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:46,617 [418] INFO     pulse.iterate: Current control: f_20 = 1.422
2025-06-06 10:59:46,618 [418] INFO     pulse.iterate: Target: 1.600
2025-06-06 10:59:50,760 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:50,761 [418] INFO     pulse.iterate: Current control: f_20 = 0.000
2025-06-06 10:59:50,761 [418] INFO     pulse.iterate: Target: 0.178
2025-06-06 10:59:55,055 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:55,056 [418] INFO     pulse.iterate: Current control: f_20 = 0.178
2025-06-06 10:59:55,056 [418] INFO     pulse.iterate: Target: 0.356
2025-06-06 10:59:59,420 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 10:59:59,421 [418] INFO     pulse.iterate: Current control: f_20 = 0.356
2025-06-06 10:59:59,421 [418] INFO     pulse.iterate: Target: 0.533
2025-06-06 11:00:03,302 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 11:00:03,303 [418] INFO     pulse.iterate: Current control: f_20 = 0.533
2025-06-06 11:00:03,303 [418] INFO     pulse.iterate: Target: 0.711
2025-06-06 11:00:07,224 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 11:00:07,225 [418] INFO     pulse.iterate: Current control: f_20 = 0.711
2025-06-06 11:00:07,225 [418] INFO     pulse.iterate: Target: 0.889
2025-06-06 11:00:11,621 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 11:00:11,622 [418] INFO     pulse.iterate: Current control: f_20 = 0.889
2025-06-06 11:00:11,623 [418] INFO     pulse.iterate: Target: 1.067
2025-06-06 11:00:16,001 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 11:00:16,002 [418] INFO     pulse.iterate: Current control: f_20 = 1.067
2025-06-06 11:00:16,002 [418] INFO     pulse.iterate: Target: 1.244
2025-06-06 11:00:19,432 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 11:00:19,432 [418] INFO     pulse.iterate: Current control: f_20 = 1.244
2025-06-06 11:00:19,433 [418] INFO     pulse.iterate: Target: 1.422
2025-06-06 11:00:23,826 [418] INFO     pulse.iterate: Iterating to target control (f_20)...
2025-06-06 11:00:23,827 [418] INFO     pulse.iterate: Current control: f_20 = 1.422
2025-06-06 11:00:23,828 [418] INFO     pulse.iterate: Target: 1.600
../_images/b5fb17417779b650afa74b84ee24f5be90d3f1dfa38657dd4a81e8833740aa2b.png
vs, ps = klotz_curve(ED_pressure)
ax.plot(vs, ps, linestyle="--", label="Klotz curve")
ax.legend(loc="best")
ax.set_xlabel("Volume (ml)")
ax.set_ylabel("Pressure (kPa)")
plt.show()
# plt.savefig("klotz_curve.png")