Single-cell APD restitution

Single-cell APD restitution#

TBW

import matplotlib.pyplot as plt
import ap_features as apf
from pathlib import Path
import numpy as np
import gotranx
from beat.single_cell import get_steady_state, compute_hash


here = Path.cwd()
model_path = Path("tentusscher_panfilov_2006_epi_cell.py")
if not model_path.is_file():
    ode = gotranx.load_ode(
        here
        / ".."
        / "odes"
        / "tentusscher_panfilov_2006"
        / "tentusscher_panfilov_2006_epi_cell.ode"
    )
    code = gotranx.cli.gotran2py.get_code(
        ode, scheme=[gotranx.schemes.Scheme.forward_generalized_rush_larsen]
    )
    model_path.write_text(code)

import tentusscher_panfilov_2006_epi_cell

model = tentusscher_panfilov_2006_epi_cell.__dict__
2025-04-02 20:53:19 [info     ] Load ode /__w/fenics-beat/fenics-beat/demos/../odes/tentusscher_panfilov_2006/tentusscher_panfilov_2006_epi_cell.ode
2025-04-02 20:53:19 [info     ] Num states 19                 
2025-04-02 20:53:19 [info     ] Num parameters 53             
def run(outdir, parameters):

    # Pre-beats
    y = get_steady_state(
        fun=model["forward_generalized_rush_larsen"],
        init_states=model["init_state_values"](),
        parameters=parameters,
        outdir=outdir / "prebeats",
        track_indices=track_indices,
        nbeats=prebeats,
        save_every_ms=save_every_ms,
        BCL=BCL,
        dt=dt,
    )

    CIs = np.arange(CI1, CI0 - CIinc, -CIinc)
    APDs = []
    Vs = []
    stim_period_index = model["parameter_index"]("stim_period")
    # APD restitution
    for CI in CIs:
        parameters[stim_period_index] = CI
        print("\nCI:", CI)
        out = outdir / f"CI_{CI}"
        hash_input = compute_hash(
            fun=model["forward_generalized_rush_larsen"],
            init_states=y,
            parameters=parameters,
            nbeats=nbeats,
            BCL=CI,
            dt=dt,
        )
        y = get_steady_state(
            fun=model["forward_generalized_rush_larsen"],
            init_states=y,
            parameters=parameters,
            outdir=out,
            track_indices=track_indices,
            nbeats=nbeats,
            save_every_ms=save_every_ms,
            BCL=CI,
            dt=dt,
        )

        traced_values_file = out / f"tracked_values_{hash_input}.npy"
        N = int(np.ceil(CI / save_every_ms))

        V = np.load(traced_values_file)[-N:, 0]
        t = np.linspace(0, CI, N)
        Vs.append(V)

        APD = apf.apd(factor=90, V=V[-N:], t=t)

        APDs.append(APD)
        print("APD:", APD)

    return APDs, CIs, Vs
prebeats = 1  # Use 50 or more
BCL = 1000
nbeats = 1  # Use maybe 7 - 8 beats here
CI0 = 200  # Final interval - typically 200 ms
# How much to decrease the interval by each step
# Typical value here is 25
CIinc = 100
CI1 = BCL
outdir = here / "apd_restitution"
outdir.mkdir(exist_ok=True, parents=True)
track_indices = [model["state_index"]("V"), model["state_index"]("Ca_i")]
save_every_ms = 2.0
dt = 0.05
APDs_normal, CIs, Vs = run(
    outdir=outdir / "normal",
    parameters=model["init_parameter_values"](),
)
fig, ax = plt.subplots()
ax.plot(CIs, APDs_normal, "o-")
ax.set_xlabel("DI (ms)")
ax.set_ylabel("APD90 (ms)")
fig.savefig(outdir / "apd_restitution_normal.png")
CI: 1000
APD: 309.0793314967153

CI: 900
APD: 308.7256308126628

CI: 800
APD: 307.65335594977097

CI: 700
APD: 305.98308565701615

CI: 600
APD: 302.9092060800135

CI: 500
APD: 296.62692684077183

CI: 400
APD: 282.8745336095068

CI: 300
APD: 252.9902812534158

CI: 200
Warning: only one root was found for APD 90
APD: 0.0
../_images/de0aebcc9313810ac0fc20b91b2aacd39d45588b12a894450669ad4e5d1ebf1c.png
step = 3
fig_v, ax_v = plt.subplots(1, 3, sharex=True, sharey=True)
../_images/5f2404b2cc9b9ff1ab92f8dbf02bd5d485d21c4e9b0c1d3859fb089c39c3be28.png
lines = []
labels = []
for i, V in enumerate(Vs[::step]):
    j = i * step
    (l,) = ax_v[0].plot(np.linspace(0, CIs[j], len(V)), V)
    lines.append(l)
    labels.append(f"{CIs[j]} ms")
ax_v[0].set_title("Normal")
Text(0.5, 1.0, 'Normal')
APDs_step1, CIs, Vs = run(
    outdir=outdir / "step1",
    parameters=model["init_parameter_values"](g_Kr=0.172, g_Ks=0.441),
)
fig, ax = plt.subplots()
ax.plot(CIs, APDs_step1, "o-")
ax.set_xlabel("DI (ms)")
ax.set_ylabel("APD90 (ms)")
fig.savefig(outdir / "apd_restitution_step1.png")
CI: 1000
APD: 296.25700564689345

CI: 900
APD: 295.83283698538116

CI: 800
APD: 294.78544095764596

CI: 700
APD: 293.2246376400637

CI: 600
APD: 290.43069744976157

CI: 500
APD: 284.81049391551227

CI: 400
APD: 272.58927712451083

CI: 300
APD: 245.7745519436981

CI: 200
Warning: only one root was found for APD 90
APD: 0.0
../_images/3e228824a73b8a023357984abd1adade4672f42ca8727556fccfe9fbccea616f.png
for i, V in enumerate(Vs[::step]):
    j = i * step
    ax_v[1].plot(np.linspace(0, CIs[j], len(V)), V)
ax_v[1].set_title("Step 1")
Text(0.5, 1.0, 'Step 1')
APDs_step2, CIs, Vs = run(
    outdir=outdir / "step2",
    parameters=model["init_parameter_values"](g_Kr=0.134, g_Ks=0.270),
)
fig, ax = plt.subplots()
ax.plot(CIs, APDs_step2, "o-")
ax.set_xlabel("DI (ms)")
ax.set_ylabel("APD90 (ms)")
fig.savefig(outdir / "apd_restitution_step2.png")
CI: 1000
APD: 341.83958208228876

CI: 900
APD: 341.65729965923873

CI: 800
APD: 340.3894339383603

CI: 700
APD: 338.20551419217236

CI: 600
APD: 333.98637378205456

CI: 500
APD: 325.2304683496414

CI: 400
APD: 306.3239956535332

CI: 300
APD: 267.8870460618192

CI: 200
Warning: only one root was found for APD 90
APD: 0.0
../_images/4755b36e0121d6437c43f2fabcb0c8391ecb238a6abe79d67834fd663e60ea5d.png
for i, V in enumerate(Vs[::step]):
    j = i * step
    ax_v[2].plot(np.linspace(0, CIs[j], len(V)), V)
ax_v[2].set_title("Step 2")
Text(0.5, 1.0, 'Step 2')
fig, ax = plt.subplots()
ax.plot(CIs, APDs_normal, "o-", label="normal")
ax.plot(CIs, APDs_step1, "o-", label="step1")
ax.plot(CIs, APDs_step2, "o-", label="step2")
ax.set_xlabel("DI (ms)")
ax.set_ylabel("APD90 (ms)")
ax.legend()
fig.savefig(outdir / "apd_restitution.png")
../_images/4d4a250b58ee2aef31261d2b4216106275697a51d3d899f875bd79e844bf809a.png
for axi in ax_v:
    axi.set_xlabel("Time (ms)")
    axi.grid()
ax_v[0].set_ylabel("V (mV)")
lgd = fig_v.legend(lines, labels, loc="center left", bbox_to_anchor=(1, 0.5))
fig_v.tight_layout()
fig_v.savefig(outdir / "V.png", bbox_extra_artists=(lgd,), bbox_inches="tight")