Set contractility, diastolic stiffness, arterial load and filling pressure to observe how the pressure-volume loop, Frank–Starling cardiac function curve, and 3D EF and stroke work surfaces respond in unison.
The charting library could not load. Please check your connection and reload the page.
(a) Pressure–Volume Loops
EDPVRESPVREa
(b) Frank–Starling & SW Function
Stroke work (left)PVAiso (right)Hover point to trace loop
(c) 3D Ejection Fraction Landscape
Ees vs α → EF (%) · Drag to rotate
(d) 3D Stroke Work Landscape
Ees vs α → SW (mmHg·mL) · Drag to rotate
Single-Beat Calculation Steps (Chen & Klotz)
Derived from bedside non-invasive inputs
Ventricle State
EDP
EDV
ESV
SV
EF
BP
Ea
Ea/Ees
SW
PVAiso
CO
mmHg
mL
mL
mL
%
mmHg
mmHg/mL
mmHg·mL
mmHg·mL
L/min
Biophysical Coupling: Time-varying elastance model P(V,t) = e(t)·Ees·(V−V0) + (1−e(t))·β·(e^(α(V−V0)) − 1). Activation e(t) rises smoothly to 1 at end-systole and relaxes with time constant τ. The ventricle fills from the left atrium through a mitral resistance (Rmv) and ejects into a 3-element Windkessel (Zc, R, C). The Frank–Starling curve sweeps filling pressure from 2 to 30 mmHg; each point represents the area of one steady-state loop. The 3D surfaces map EF and stroke work across contractility (Ees) and diastolic stiffness (α) with arterial load held constant.
Varying EDPVR at Fixed ESPVR
Ees is held constant at 2.0 mmHg/mL while diastolic stiffness constant α is stepped. Notice how stroke work collapses steeply by >50% as the chamber stiffens, while ejection fraction remains deceptively stable.
(a) PV Loops at LAP 12 mmHg
Hover (c) to trace loop here
(b) Preload Family (LAP 5, 12, 25 mmHg)
(c) Frank–Starling & EDP–PVAiso
SWPVAiso
(d) Hemodynamic Results at LAP 12 mmHg
Ventricle
EDV
ESV
SV
EF %
BP
Ea
SW
PVAiso
Volumes in mL, BP in mmHg, Ea in mmHg/mL, SW in mmHg·mL.
Varying ESPVR at Fixed EDPVR
Diastolic stiffness α is fixed at 0.030 /mL while contractility Ees is stepped. Filling volume remains stable, so inotropic loss manifests directly as an immediate fall in ejection fraction.
(a) PV Loops at LAP 12 mmHg
Hover (c) to trace loop here
(b) Preload Family (LAP 5, 12, 25 mmHg)
(c) Frank–Starling & EDP–PVAiso
SWPVAiso
(d) Hemodynamic Results at LAP 12 mmHg
Ventricle
EDV
ESV
SV
EF %
BP
Ea
SW
PVAiso
Volumes in mL, BP in mmHg, Ea in mmHg/mL, SW in mmHg·mL.
Coupled Topography: EF vs. Stroke Work
Steady-state simulations per grid point across Ees and EDPVR α at matched LAP 12 mmHg. The five benchmark dots represent the reference archetypes.
(a) Ejection Fraction Surface
(b) Stroke Work Surface
pv_frank_starling_plotly.py
The time-varying elastance differential equation engine written in Python with Plotly.
Standalone Python implementation.
"""
PV loops <-> Frank-Starling curves -- interactive Plotly version
================================================================
Same biophysical model (time-varying elastance LV + Windkessel),
so blood pressure is an emergent output, not an assumption.
"""
import numpy as np
import plotly.graph_objects as go
DEFAULTS = dict(Ees=2.0, alpha=0.030, beta=0.5, V0=10.0,
LAP=12.0, Rmv=0.02, R=1.15, C=1.6, Zc=0.05, HR=75.0, tau=0.035)
DT = 5e-4
def activation(HR, tau):
T = 60.0 / HR
Tes = 0.15 + 0.2 * T
t = np.arange(int(round(T / DT))) * DT
return np.where(t < Tes, 0.5 * (1 - np.cos(np.pi * t / Tes)), np.exp(-(t - Tes) / tau))
def simulate(Pc0=None, **p):
q = dict(DEFAULTS); q.update(p)
e = activation(q["HR"], q["tau"]); n = len(e)
Ees, a, b, V0, LAP = q["Ees"], q["alpha"], q["beta"], q["V0"], q["LAP"]
V = V0 + np.log(LAP / b + 1) / a
Pc = 85.0 if Pc0 is None else Pc0
Vs, Ps, Pa = np.empty(n), np.empty(n), np.empty(n)
last = None
for _ in range(16):
Pc_start = Pc
for i in range(n):
x = V - V0
P = e[i] * Ees * x + (1 - e[i]) * b * (np.exp(a * x) - 1)
Qm = (LAP - P) / q["Rmv"] if P < LAP else 0.0
Qa = (P - Pc) / q["Zc"] if P > Pc else 0.0
Vs[i], Ps[i], Pa[i] = V, P, Pc + q["Zc"] * Qa
V += DT * (Qm - Qa)
Pc += DT * (Qa - Pc / q["R"]) / q["C"]
cur = (Vs.max(), Vs.min())
if last and abs(cur[0] - last[0]) < .05 and abs(cur[1] - last[1]) < .05 and abs(Pc - Pc_start) < .2:
break
last = cur
iE = int(np.argmax(Vs)); edv, esv = Vs[iE], Vs.min(); sv = edv - esv
pes = Ees * (esv - V0)
return dict(V=Vs.copy(), P=Ps.copy(), EDV=edv, ESV=esv, SV=sv, EF=100 * sv / edv,
EDP=Ps[iE], Pes=pes, SBP=Pa.max(), DBP=Pa.min(), Ea=pes / sv,
SW=0.5 * abs(np.dot(Vs, np.roll(Ps, -1)) - np.dot(Ps, np.roll(Vs, -1))))