Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import pandas as pd | |
| from .heart import get_wall_thickness, set_total_wall_volumes_areas, unloaded_heart_volume | |
| from pathlib import Path | |
| import os | |
| def clear_converged_sol(): | |
| """Clear last converged solution""" | |
| root_dir = Path(__file__).parent.parent.absolute() | |
| f_converged_sol = Path(os.path.join(root_dir, "io", 'converged_sol.json')) | |
| if f_converged_sol.is_file(): | |
| os.remove(f_converged_sol) | |
| def strain_outputs(model, outputs, time_g=0, dense_time_frame=50): | |
| """Calculate strain outputs from model""" | |
| time_events = get_valve_events(model) | |
| es_frame = time_events['av_closes'] | |
| ed_frame = time_events['mv_closes'] | |
| n_patch = len(model.heart.patches) | |
| for i in range(n_patch): | |
| if ed_frame < es_frame: | |
| outputs["MinSysStrain_s" + str(i)] = np.minimum(np.min(model.heart.lab_f[:ed_frame, i]), | |
| np.min(model.heart.lab_f[es_frame:, i])) | |
| outputs["MinDiaStrain_s" + str(i)] = np.min(model.heart.lab_f[ed_frame:es_frame, i]) | |
| outputs["DelSysStrain_s" + str(i)] = np.maximum(np.max(model.heart.lab_f[:ed_frame, i]), | |
| np.max(model.heart.lab_f[es_frame:, i])) - np.minimum( | |
| np.min(model.heart.lab_f[:ed_frame, i]), np.min(model.heart.lab_f[es_frame:, i])) | |
| outputs["DelDiaStrain_s" + str(i)] = np.max(model.heart.lab_f[ed_frame:es_frame, i]) - np.min( | |
| model.heart.lab_f[ed_frame:es_frame, i]) | |
| outputs["MeanSysStrain_s" + str(i)] = (np.mean(model.heart.lab_f[:ed_frame, i]) + np.mean( | |
| model.heart.lab_f[es_frame:, i])) / 2 | |
| outputs["MeanDiaStrain_s" + str(i)] = np.mean(model.heart.lab_f[ed_frame:es_frame, i]) | |
| else: | |
| outputs["MinSysStrain_s" + str(i)] = np.min( | |
| model.heart.lab_f[es_frame:ed_frame, i]) | |
| # outputs[["MinDiaStrain_s" + str(i)]] = np.min( | |
| # model.heart.lab_f[ed_frame:es_frame, i]) | |
| outputs["DelSysStrain_s" + str(i)] = np.max( | |
| model.heart.lab_f[es_frame:ed_frame, i]) - np.min(model.heart.lab_f[es_frame:ed_frame, i]) | |
| # outputs[["DelDiaStrain_s" + str(i)]] = np.max( | |
| # model.heart.lab_f[ed_frame:es_frame, i]) - np.min(model.heart.lab_f[ed_frame:es_frame, i]) | |
| outputs["MeanSysStrain_s" + str(i)] = np.mean( | |
| model.heart.lab_f[es_frame:ed_frame, i]) | |
| # outputs[["MeanDiaStrain_s" + str(i)]] = np.mean( | |
| # model.heart.lab_f[ed_frame:es_frame, i]) | |
| max_strain = np.max(model.heart.lab_f[:, i]) | |
| del_strain = max_strain - np.min(model.heart.lab_f[:, i]) | |
| t_s10 = np.zeros(n_patch) | |
| t_s50 = np.zeros(n_patch) | |
| t_s90 = np.zeros(n_patch) | |
| for n in range(dense_time_frame - 1): | |
| t = int((n + 1) * model.solver.n_inc / dense_time_frame) | |
| if model.heart.lab_f[t, i] < (max_strain - 0.1 * del_strain) and t_s10[i] == 0: | |
| t_s10[i] = n | |
| if model.heart.lab_f[t, i] < (max_strain - 0.5 * del_strain) and t_s50[i] == 0: | |
| t_s50[i] = n | |
| if model.heart.lab_f[t, i] < (max_strain - 0.9 * del_strain) and t_s90[i] == 0: | |
| t_s90[i] = n | |
| outputs["TS10_s" + str(i)] = t_s10[i] | |
| outputs["TS50_s" + str(i)] = t_s50[i] | |
| outputs["TS90_s" + str(i)] = t_s90[i] | |
| return outputs | |
| def get_outputs(model, time_g=0, match_strain=False): | |
| """Collect model outputs in Pandas dataframe""" | |
| # Get valve opening and closing events and ventricle timing events (isovolumic contraction etc.) | |
| time_events = get_valve_events(model) | |
| # Get wall thicknesses and midwall areas | |
| wall_thickness, a_m_w = get_wall_thickness(model) | |
| # LV ES and ED | |
| es_frame = time_events['av_closes'] | |
| ed_frame = time_events['mv_closes'] | |
| es_time = model.time[es_frame] | |
| esp = model.pressures[es_frame, 2] # [mmHg] | |
| esv = model.volumes[es_frame, 2] # [mL] | |
| ed_time = model.time[ed_frame] | |
| edp = model.pressures[ed_frame, 2] # [mmHg] | |
| edv = model.volumes[ed_frame, 2] # [mL] | |
| # RV ES and ED | |
| es_frame_rv = time_events['rv_closes'] | |
| ed_frame_rv = time_events['tv_closes'] | |
| es_time_rv = model.time[es_frame_rv] | |
| esp_rv = model.pressures[es_frame_rv, 6] # [mmHg] | |
| esv_rv = model.volumes[es_frame_rv, 6] # [mL] | |
| ed_time_rv = model.time[ed_frame_rv] | |
| edp_rv = model.pressures[ed_frame_rv, 6] # [mmHg] | |
| edv_rv = model.volumes[ed_frame_rv, 6] # [mL] | |
| # Maximum pressures and gradients for LV and RV | |
| p_max = max(model.pressures[:, 2]) # [mmHg] | |
| dpdt_max = max(np.gradient(model.pressures[:, 2], model.time[1] - model.time[0])) # [mmHg/s] | |
| dpdt_min = min(np.gradient(model.pressures[:, 2], model.time[1] - model.time[0])) # [mmHg/s] | |
| p_max_rv = max(model.pressures[:, 6]) # [mmHg] | |
| dpdt_max_rv = max(np.gradient(model.pressures[:, 6], model.time[1] - model.time[0])) # [mmHg/s] | |
| dpdt_min_rv = min(np.gradient(model.pressures[:, 6], model.time[1] - model.time[0])) # [mmHg/s] | |
| # ED wall thickness [mm] | |
| ed_wth = wall_thickness[ed_frame] | |
| es_wth = wall_thickness[es_frame] | |
| # LV and RV Wall Volume | |
| lvwv = np.sum(model.heart.vw[model.heart.patches == 0]) + np.sum(model.heart.vw[model.heart.patches == 2]) | |
| rvwv = np.sum(model.heart.vw[model.heart.patches == 1]) | |
| # ED wall stretch | |
| lab_ed_lfw = np.mean(model.heart.lab_f[ed_frame, model.heart.patches == 0]) | |
| lab_ed_rfw = np.mean(model.heart.lab_f[ed_frame, model.heart.patches == 1]) | |
| lab_ed_sw = np.mean(model.heart.lab_f[ed_frame, model.heart.patches == 2]) | |
| lab_ed_la = np.mean(model.heart.lab_f[ed_frame, model.heart.patches == 3]) | |
| lab_ed_ra = np.mean(model.heart.lab_f[ed_frame, model.heart.patches == 4]) | |
| # Maximum stretch and stress | |
| lab_max_lfw = np.max(model.heart.lab_f[:, model.heart.patches == 0]) | |
| lab_max_rfw = np.max(model.heart.lab_f[:, model.heart.patches == 1]) | |
| lab_max_sw = np.max(model.heart.lab_f[:, model.heart.patches == 2]) | |
| lab_max_la = np.max(model.heart.lab_f[:, model.heart.patches == 3]) | |
| lab_max_ra = np.max(model.heart.lab_f[:, model.heart.patches == 4]) | |
| sig_max_lfw = np.max(model.heart.sig_f[:, model.heart.patches == 0]) | |
| sig_max_rfw = np.max(model.heart.sig_f[:, model.heart.patches == 1]) | |
| sig_max_sw = np.max(model.heart.sig_f[:, model.heart.patches == 2]) | |
| sig_max_la = np.max(model.heart.sig_f[:, model.heart.patches == 3]) | |
| sig_max_ra = np.max(model.heart.sig_f[:, model.heart.patches == 4]) | |
| # Cardiac function of LV and RV | |
| sv = edv - esv # [mL] | |
| sv_rv = edv_rv - esv_rv # [mL] | |
| ef = (edv - esv) / edv # [-] | |
| ef_rv = (edv_rv - esv_rv) / edv_rv # [-] | |
| co = sv / model.time[-1] * 60 / 1e3 # [L/min] | |
| co_rv = sv_rv / model.time[-1] * 60 / 1e3 # [L/min] | |
| hr = 60 / model.time[-1] # [s] | |
| # Calculate regurgitation fraction | |
| rf, rf_rv = get_rf(model) | |
| # Arterial function | |
| map = model.pressures[:, 3].mean() | |
| sbp = max(model.pressures[:, 3]) | |
| dbp = min(model.pressures[:, 3]) | |
| # Get geometry: LV-SW endocardial distance, endocardial RV-SW distance, and distance between RV insertions | |
| # Needs to be improved: now half of each wall thickness is substracted but this is not accurate at midwall radius | |
| dlv_sw = model.heart.xm[ed_frame, 2] - model.heart.xm[ed_frame, 0] - 0.5*(ed_wth[0] + ed_wth[2]) | |
| drv_sw = model.heart.xm[ed_frame, 1] - model.heart.xm[ed_frame, 2] - 0.5*(ed_wth[2] + ed_wth[1]) | |
| drvi = model.heart.ys_store[ed_frame] | |
| # ED and ES diameter for LV and RV | |
| lvedd = 2*(abs(model.heart.rm[ed_frame, 0]) - ed_wth[0]) | |
| rvedd = 2*(abs(model.heart.rm[ed_frame_rv, 1]) - ed_wth[1]) | |
| lvesd = 2*(abs(model.heart.rm[es_frame, 0]) - es_wth[0]) | |
| rvesd = 2*(abs(model.heart.rm[es_frame_rv, 0]) - es_wth[1]) | |
| lvfs = (lvedd - lvesd) / lvedd * 100 | |
| rvfs = (rvedd - rvesd) / rvedd * 100 | |
| # mean left atrial pressure | |
| lap = model.pressures[:, 1].mean() | |
| # indexed volumes | |
| if model.circulation.bsa is not None: | |
| edv_i = edv / model.circulation.bsa | |
| esv_i = esv / model.circulation.bsa | |
| rvedv_i = edv_rv / model.circulation.bsa | |
| rvesv_i = esv_rv / model.circulation.bsa | |
| else: | |
| edv_i = np.nan | |
| esv_i = np.nan | |
| rvedv_i = np.nan | |
| rvesv_i = np.nan | |
| # Stroke work done by the LV | |
| work = (p_max - min(model.pressures[:, 2])) * sv # [mmHg*mL] | |
| # Work for each wall | |
| strain_lfw = np.mean(0.5*(model.heart.lab_f[:, model.heart.patches == 0]**2 - 1), axis=1) # [-] Green strain for each time step averaged over all segments | |
| sig_lfw = np.mean(model.heart.sig_f[:, model.heart.patches == 0], axis=1)*1000000 # [Pa] stress for each time step averaged over all segments | |
| work_lfw = np.trapz(sig_lfw, x=strain_lfw) # [J] work of the LV free wall during one cardiac cycle | |
| strain_rfw = np.mean(0.5*(model.heart.lab_f[:, model.heart.patches == 1]**2 - 1), axis=1) | |
| sig_rfw = np.mean(model.heart.sig_f[:, model.heart.patches == 1], axis=1)*1000000 | |
| work_rfw = np.trapz(sig_rfw, x=strain_rfw) | |
| strain_sw = np.mean(0.5*(model.heart.lab_f[:, model.heart.patches == 2]**2 - 1), axis=1) | |
| sig_sw = np.mean(model.heart.sig_f[:, model.heart.patches == 2], axis=1)*1000000 | |
| work_sw = np.trapz(sig_sw, x=strain_sw) | |
| # aorta-to-vein pressure drop (LVCO / Ras) | |
| co_ras = co / model.resistances.ras | |
| # Turn into pandas | |
| outputs = pd.DataFrame([[edv, esv, edp, esp, p_max, dpdt_max, dpdt_min, sv, rf, ef, co, | |
| edv_rv, esv_rv, edp_rv, esp_rv, p_max_rv, dpdt_max_rv, dpdt_min_rv, sv_rv, rf_rv, | |
| ef_rv, co_rv, ed_wth[0], ed_wth[1], ed_wth[2], es_wth[0], es_wth[1], es_wth[2], | |
| dlv_sw, drv_sw, drvi, dlv_sw/drv_sw, dlv_sw/drvi, drv_sw/drvi, lvwv, rvwv, | |
| lvedd, lvesd, lvfs, rvedd, rvesd, rvfs, | |
| lab_ed_lfw, lab_ed_rfw, lab_ed_sw, lab_ed_la, lab_ed_ra, | |
| lab_max_lfw, lab_max_rfw, lab_max_sw, lab_max_la, lab_max_ra, | |
| sig_max_lfw, sig_max_rfw, sig_max_sw, sig_max_la, sig_max_ra, | |
| time_events['LVIVCT'], time_events['LVIVRT'], time_events['LVET'], time_events['LVFT'], | |
| time_events['RVIVCT'], time_events['RVIVRT'], time_events['RVET'], time_events['RVFT'], | |
| map, hr, dbp, sbp, ed_frame, es_frame, ed_time, es_time, ed_frame_rv, es_frame_rv, | |
| ed_time_rv, es_time_rv, lap, | |
| edv_i, esv_i, rvedv_i, rvesv_i, work, work_lfw, work_sw, work_rfw, co_ras], ], | |
| columns=['LVEDV', 'LVESV', 'LVEDP', 'LVESP', 'LVMaxP', 'LVMaxdP', 'LVMindP', 'LVSV', 'LVRF', 'LVEF', 'LVCO', | |
| 'RVEDV', 'RVESV', 'RVEDP', 'RVESP', 'RVMaxP', 'RVMaxdP', 'RVMindP', 'RVSV', 'RVRF', 'RVEF', 'RVCO', | |
| 'EDWthLfw', 'EDWthRfw', 'EDWthSw', 'ESWthLfw', 'ESWthRfw', 'ESWthSw', | |
| 'Dlvsw', 'Drvsw', 'Drvi', 'DlvswDrvsw', 'DlvswDrvi', 'DrvswDrvi', 'LVWV', 'RVWV', | |
| 'LVEDD', 'LVESD', 'LVFS', 'RVEDD', 'RVESD', 'RVFS', | |
| 'EDStretchLfw', 'EDStretchRfw', 'EDStretchSw', 'EDStretchLA', 'EDStretchRA', | |
| "MaxStretchLfw", "MaxStretchRfw", "MaxStretchSw", "MaxStretchLA", "MaxStretchRA", | |
| "MaxStressLfw", "MaxStressRfw", "MaxStressSw", "MaxStressLA", "MaxStressRA", | |
| 'LVIVCT', 'LVIVRT', 'LVET', 'LVFT', | |
| 'RVIVCT', 'RVIVRT', 'RVET', 'RVFT', | |
| 'MAP', 'HR', 'DBP', 'SBP', | |
| 'IED', 'IES', 'TED', 'TES', 'IED_RV', 'IES_RV', 'TED_RV', 'TES_RV', 'LAP', | |
| 'LVEDVi', 'LVESVi', 'RVEDVi', 'RVESVi', 'Work', 'WorkLfw', 'WorkSw', 'WorkRfw', 'CO_Ras'], | |
| index=[time_g]) | |
| if match_strain: | |
| outputs = strain_outputs(model, outputs, time_g) | |
| return outputs | |
| def get_valve_events(model): | |
| """Determine when valves are open (0) and closed (1) based on transvalvular pressure and calculate ventricle | |
| timings""" | |
| p = model.pressures | |
| time_events = { | |
| "mv_closes": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 2] > p[:, 1], 1)), 0)), | |
| "mv_opens": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 2] < p[:, 1], 1)), 0)), | |
| "av_closes": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 3] > p[:, 2], 1)), 0)), | |
| "av_opens": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 3] < p[:, 2], 1)), 0)), | |
| "tv_closes": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 6] > p[:, 5], 1)), 0)), | |
| "tv_opens": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 6] < p[:, 5], 1)), 0)), | |
| "rv_closes": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 7] > p[:, 6], 1)), 0)), | |
| "rv_opens": p.shape[0] - 1 - np.argmax(np.flip(np.diff(np.multiply(p[:, 7] < p[:, 6], 1)), 0)) | |
| } | |
| # Isovolumic contraction | |
| if time_events['av_opens'] > time_events['mv_closes']: | |
| time_events['LVIVCT'] = (model.time[time_events['av_opens']] - model.time[time_events['mv_closes']])*1e3 | |
| else: | |
| time_events['LVIVCT'] = (model.time[-1] - model.time[time_events['mv_closes']] + model.time[time_events['av_opens']])*1e3 | |
| if time_events['rv_opens'] > time_events['tv_closes']: | |
| time_events['RVIVCT'] = (model.time[time_events['rv_opens']] - model.time[time_events['tv_closes']]) * 1e3 | |
| else: | |
| time_events['RVIVCT'] = (model.time[-1] - model.time[time_events['rv_closes']] + model.time[time_events['rv_opens']]) * 1e3 | |
| # Isovolumic relaxation | |
| if time_events['av_closes'] < time_events['mv_opens']: | |
| time_events['LVIVRT'] = (model.time[time_events['mv_opens']] - model.time[time_events['av_closes']])*1e3 | |
| else: | |
| time_events['LVIVRT'] = (model.time[-1] - model.time[time_events['av_closes']] + model.time[time_events['mv_opens']])*1e3 | |
| if time_events['rv_closes'] < time_events['tv_opens']: | |
| time_events['RVIVRT'] = (model.time[time_events['tv_opens']] - model.time[time_events['rv_closes']])*1e3 | |
| else: | |
| time_events['RVIVRT'] = (model.time[-1] - model.time[time_events['rv_closes']] + model.time[time_events['tv_opens']])*1e3 | |
| # Ejection time | |
| if time_events['av_closes'] > time_events['av_opens']: | |
| time_events['LVET'] = (model.time[time_events['av_closes']] - model.time[time_events['av_opens']])*1e3 | |
| else: | |
| time_events['LVET'] = (model.time[-1] - model.time[time_events['av_opens']] + model.time[time_events['av_closes']])*1e3 | |
| if time_events['av_closes'] > time_events['av_opens']: | |
| time_events['RVET'] = (model.time[time_events['rv_closes']] - model.time[time_events['rv_opens']])*1e3 | |
| else: | |
| time_events['RVET'] = (model.time[-1] - model.time[time_events['rv_opens']] + model.time[time_events['rv_closes']])*1e3 | |
| # Filling time | |
| if time_events['mv_closes'] > time_events['mv_opens']: | |
| time_events['LVFT'] = (model.time[time_events['mv_closes']] - model.time[time_events['mv_opens']])*1e3 | |
| else: | |
| time_events['LVFT'] = (model.time[-1] - model.time[time_events['mv_opens']] + model.time[time_events['mv_closes']])*1e3 | |
| if time_events['tv_opens'] > time_events['tv_closes']: | |
| time_events['RVFT'] = (model.time[time_events['tv_closes']] - model.time[time_events['tv_opens']])*1e3 | |
| else: | |
| time_events['RVFT'] = (model.time[-1] - model.time[time_events['tv_opens']] + model.time[time_events['tv_closes']])*1e3 | |
| return time_events | |
| def get_rf(model): | |
| """Comute regurgitant fraction of the LV and RV, based on flow equations from beat_it""" | |
| # Regurgitant volume: integrate flow rate from LV to LA | |
| dvr = ((model.pressures[:, 2] - model.pressures[:, 1]) / | |
| model.resistances.rmvb * (model.pressures[:, 2] > model.pressures[:, 1])) | |
| v_regurgitant = np.trapz(dvr, dx=model.solver.dt) | |
| # Forward volume: integrate flow rate from LV to systemic arteries | |
| dvf = (model.pressures[:, 2] - model.pressures[:, 3]) / model.resistances.rcs * (model.pressures[:, 2] > model.pressures[:, 3]) | |
| v_forward = np.trapz(dvf, dx=model.solver.dt) | |
| # Compute regurgitant fraction, prevent dividing by zero if the heart is really really broken | |
| if v_forward == 0: | |
| rf = 0 | |
| else: | |
| rf = v_regurgitant / (v_forward + v_regurgitant) | |
| # Regurgitant volume: integrate flow rate from RV to RA | |
| dvr = ((model.pressures[:, 6] - model.pressures[:, 5]) / | |
| model.resistances.rtvb * (model.pressures[:, 6] > model.pressures[:, 5])) | |
| v_regurgitant = np.trapz(dvr, dx=model.solver.dt) | |
| # Forward volume: integrate flow rate from RV to pulmonary arteries | |
| dvf = (model.pressures[:, 6] - model.pressures[:, 7]) / model.resistances.rcp * ( | |
| model.pressures[:, 6] > model.pressures[:, 7]) | |
| v_forward = np.trapz(dvf, dx=model.solver.dt) | |
| # Compute regurgitant fraction, prevent dividing by zero if the heart is really really broken | |
| if v_forward == 0: | |
| rf_rv = 0 | |
| else: | |
| rf_rv = v_regurgitant / (v_forward + v_regurgitant) | |
| return rf, rf_rv | |
| def change_pars(model, pars): | |
| """Change model parameter values using a single dictionary input with par_name: par_value. Convenient for changing | |
| model parameters while fitting. Order is important: ratio-based parameter changes should occur after any absolute | |
| value changes""" | |
| # Change all key names to lowercase to prevent case inconsistencies | |
| pars = {key.lower(): value for key, value in pars.items()} | |
| # Circulation | |
| for par, value in pars.items(): | |
| if par == "sbv": | |
| model.circulation.sbv = value | |
| elif par == "hr": | |
| model.circulation.hr = value | |
| elif par == "k_initial": | |
| model.circulation.k = value | |
| # Capacitances | |
| elif par == "cvp": | |
| model.capacitances.cvp = value | |
| elif par == "cas": | |
| model.capacitances.cas = value | |
| elif par == "cap": | |
| model.capacitances.cap = value | |
| elif par == "cvs": | |
| model.capacitances.cvs = value | |
| # Resistances | |
| elif par == "rvp": | |
| model.resistances.rvp = value | |
| elif par == "rcs": | |
| model.resistances.rcs = value | |
| elif par == "ras": | |
| model.resistances.ras = value | |
| elif par == "rvs": | |
| model.resistances.rvs = value | |
| elif par == "rcp": | |
| model.resistances.rcp = value | |
| elif par == "rap": | |
| model.resistances.rap = value | |
| elif par == "rav": | |
| model.resistances.rav = value | |
| elif par == "rmvb": | |
| model.resistances.rmvb = value | |
| elif par == "rtvb": | |
| model.resistances.rtvb = value | |
| # Heart parameters, ventricles and atria (with suffix _a) separately | |
| chamber_names = ["", "a"] | |
| i_ventricles = model.heart.patches < 3 | |
| i_atria = model.heart.patches >= 3 | |
| i_chambers = [i_ventricles, i_atria] | |
| for i, chambers in enumerate(i_chambers): | |
| if par == "sf_act" + chamber_names[i]: | |
| model.heart.sf_act[chambers] = value | |
| elif par == "sfact" + chamber_names[i]: | |
| model.heart.sf_act[chambers] = value | |
| elif par == "tad" + chamber_names[i]: | |
| model.heart.t_ad[chambers] = value | |
| elif par == "td" + chamber_names[i]: | |
| model.heart.tau_d[chambers] = value | |
| elif par == "tr" + chamber_names[i]: | |
| model.heart.tau_r[chambers] = value | |
| elif par == "c1" + chamber_names[i]: | |
| model.heart.c_1[chambers] = value | |
| elif par == "c3" + chamber_names[i]: | |
| model.heart.c_3[chambers] = value | |
| elif par == "c4" + chamber_names[i]: | |
| model.heart.c_4[chambers] = value | |
| elif par == "tact" + chamber_names[i]: | |
| model.heart.t_act = value | |
| # Set parameter for specific patches | |
| if "_s" in par: | |
| # Par name and patch number | |
| patch = int(par.split("_s")[1]) | |
| par = par.split("_")[0] | |
| if par == "sact": | |
| model.heart.sf_act[patch] = value | |
| elif par == "tad": | |
| model.heart.t_ad[patch] = value | |
| elif par == "td": | |
| model.heart.tau_d[patch] = value | |
| elif par == "tr": | |
| model.heart.tau_r[patch] = value | |
| elif par == "c1": | |
| model.heart.c_1[patch] = value | |
| elif par == "c3": | |
| model.heart.c_3[patch] = value | |
| elif par == "c4": | |
| model.heart.c_4[patch] = value | |
| elif par == "tact": | |
| model.heart.t_act[patch] = value | |
| elif par == "amref": | |
| model.heart.am_ref[patch] = value | |
| elif par == "vw": | |
| model.heart.vw[patch] = value | |
| # Timing properties, atriaventricular delay and intraventricular delay (between lfw/rfw and septum) | |
| if par == "avd": | |
| model.heart.t_act[i_ventricles] = model.heart.t_act[i_ventricles] + value | |
| if par == "ivd_lv": | |
| model.heart.t_act[model.heart.patches == 0] = np.mean(model.heart.t_act[model.heart.patches == 2]) + value | |
| if par == "ivd_rv": | |
| model.heart.t_act[model.heart.patches == 1] = np.mean(model.heart.t_act[model.heart.patches == 2]) + value | |
| # Pericardium | |
| if par == "wth_p": | |
| model.pericardium.thickness = value | |
| elif par == "c1_p": | |
| model.pericardium.c_1 = value | |
| elif par == "c3_p": | |
| model.pericardium.c_3 = value | |
| elif par == "c4_p": | |
| model.pericardium.c_4 = value | |
| elif par == "prestretch": | |
| model.pericardium.pre_stretch = value | |
| # Heart area - maintain ratio of AmRefs within each wall but scale according to total AmRef given | |
| elif par == "amreflfw": | |
| model.heart.am_ref[model.heart.patches == 0] = model.heart.am_ref[model.heart.patches == 0] * \ | |
| value / np.sum(model.heart.am_ref[model.heart.patches == 0]) | |
| elif par == "amrefrfw": | |
| model.heart.am_ref[model.heart.patches == 1] = model.heart.am_ref[model.heart.patches == 1] * \ | |
| value / np.sum(model.heart.am_ref[model.heart.patches == 1]) | |
| elif par == "amrefsw": | |
| model.heart.am_ref[model.heart.patches == 2] = model.heart.am_ref[model.heart.patches == 2] * \ | |
| value / np.sum(model.heart.am_ref[model.heart.patches == 2]) | |
| elif par == "amrefla": | |
| model.heart.am_ref[model.heart.patches == 3] = model.heart.am_ref[model.heart.patches == 3] * \ | |
| value / np.sum(model.heart.am_ref[model.heart.patches == 3]) | |
| elif par == "amrefra": | |
| model.heart.am_ref[model.heart.patches == 4] = model.heart.am_ref[model.heart.patches == 4] * \ | |
| value / np.sum(model.heart.am_ref[model.heart.patches == 4]) | |
| # Wall volume, maintain current ratio in wall volumes between patches | |
| elif par == "vlfw": | |
| model.heart.vw[model.heart.patches == 0] = value * model.heart.vw[model.heart.patches == 0] / \ | |
| np.sum(model.heart.vw[model.heart.patches == 0]) | |
| elif par == "vrfw": | |
| model.heart.vw[model.heart.patches == 1] = value * model.heart.vw[model.heart.patches == 1] / \ | |
| np.sum(model.heart.vw[model.heart.patches == 1]) | |
| elif par == "vsw": | |
| model.heart.vw[model.heart.patches == 2] = value * model.heart.vw[model.heart.patches == 2] / \ | |
| np.sum(model.heart.vw[model.heart.patches == 2]) | |
| elif par == "vla": | |
| model.heart.vw[model.heart.patches == 3] = value * model.heart.vw[model.heart.patches == 3] / \ | |
| np.sum(model.heart.vw[model.heart.patches == 3]) | |
| elif par == "vra": | |
| model.heart.vw[model.heart.patches == 4] = value * model.heart.vw[model.heart.patches == 4] / \ | |
| np.sum(model.heart.vw[model.heart.patches == 4]) | |
| # Sigmoid parameters | |
| elif par == "fgmaxf+": | |
| model.growth.fgmax_f_plus = value | |
| elif par == "fgmaxf-": | |
| model.growth.fgmax_f_min = value | |
| elif par == "nf+": | |
| model.growth.n_f_plus = value | |
| elif par == "nf-": | |
| model.growth.n_f_min = value | |
| elif par == "s50f+": | |
| model.growth.s50_f_plus = value | |
| elif par == "s50f-": | |
| model.growth.s50_f_min = value | |
| elif par == "fgmaxr+": | |
| model.growth.fgmax_r_plus = value | |
| elif par == "fgmaxr-": | |
| model.growth.fgmax_r_min = value | |
| elif par == "nr+": | |
| model.growth.n_r_plus = value | |
| elif par == "nr-": | |
| model.growth.n_r_min = value | |
| elif par == "s50r+": | |
| model.growth.s50_r_plus = value | |
| elif par == "s50r-": | |
| model.growth.s50_r_min = value | |
| elif par == "t_mem": | |
| model.growth.t_mem = value | |
| # Growth parameters | |
| elif par == "tau_f-": | |
| model.growth.tau_f_min = value | |
| elif par == "tau_f_min": | |
| model.growth.tau_f_min = value | |
| elif par == "tau_f+": | |
| model.growth.tau_f_plus = value | |
| elif par == "tau_f_max": | |
| model.growth.tau_f_plus = value | |
| elif par == "tau_r-": | |
| model.growth.tau_r_min = value | |
| elif par == "tau_r+": | |
| model.growth.tau_r_plus = value | |
| elif par == "tau_r_min": | |
| model.growth.tau_r_min = value | |
| elif par == "tau_r_max": | |
| model.growth.tau_r_plus = value | |
| elif par == "theta_f_min": | |
| model.growth.theta_f_min = value | |
| elif par == "theta_f_max": | |
| model.growth.theta_f_max = value | |
| elif par == "theta_r_min": | |
| model.growth.theta_r_min = value | |
| elif par == "theta_r_max": | |
| model.growth.theta_r_max = value | |
| elif par == "gamma": | |
| model.growth.gamma = value | |
| ### The following metrics all use calculations to set wall volumes and areas, used for specific fitting schemes | |
| # Set total LV wall volume and distribute along left free wall and septal wall using patch number | |
| elif par == "lvwv": | |
| vw_tot_lv = np.sum(model.heart.vw[model.heart.patches == 0]) + np.sum( | |
| model.heart.vw[model.heart.patches == 2]) | |
| for i_wall in [0, 2]: | |
| model.heart.vw[model.heart.patches == i_wall] = model.heart.vw[model.heart.patches == i_wall] * \ | |
| value / vw_tot_lv | |
| # Set midwall reference areas using ratio with left free wall | |
| elif par == "amrefrfwratio": | |
| am_ref_rfw = value * np.sum(model.heart.am_ref[model.heart.patches == 0]) | |
| model.heart.am_ref[model.heart.patches == 1] = model.heart.am_ref[model.heart.patches == 1] * \ | |
| am_ref_rfw / np.sum(model.heart.am_ref[model.heart.patches == 1]) | |
| elif par == "amrefswratio": | |
| am_ref_sw = value * np.sum(model.heart.am_ref[model.heart.patches == 0]) | |
| model.heart.am_ref[model.heart.patches == 2] = model.heart.am_ref[model.heart.patches == 2] * \ | |
| am_ref_sw / np.sum(model.heart.am_ref[model.heart.patches == 2]) | |
| elif par == "amreflaratio": | |
| model.heart.am_ref[model.heart.patches == 3] = value * np.sum(model.heart.am_ref[model.heart.patches == 0]) | |
| elif par == "amrefraratio": | |
| model.heart.am_ref[model.heart.patches == 4] = value * np.sum(model.heart.am_ref[model.heart.patches == 0]) | |
| # Set wall volumes using ratio with left free wall | |
| elif par == "rfwvratio": | |
| vw_tot = value * np.sum(model.heart.vw[model.heart.patches == 0]) | |
| model.heart.vw[model.heart.patches == 1] = model.heart.vw[model.heart.patches == 1] * \ | |
| vw_tot / np.sum(model.heart.vw[model.heart.patches == 1]) | |
| elif par == "swvratio": | |
| vw_tot = value * np.sum(model.heart.vw[model.heart.patches == 0]) | |
| model.heart.vw[model.heart.patches == 2] = model.heart.vw[model.heart.patches == 2] * \ | |
| vw_tot / np.sum(model.heart.vw[model.heart.patches == 2]) | |
| elif par == "lawvratio": | |
| model.heart.vw[model.heart.patches == 3] = value * np.sum( | |
| model.heart.vw[model.heart.patches == 0]) | |
| elif par == "rawvratio": | |
| model.heart.vw[model.heart.patches == 4] = value * np.sum( | |
| model.heart.vw[model.heart.patches == 0]) | |
| # Set wall volumes based on specified wall thickness | |
| elif par == "lfwth": | |
| model.heart.vw[model.heart.patches == 0] = value * model.heart.am_ref[model.heart.patches == 0] | |
| elif par == "rfwth": | |
| model.heart.vw[model.heart.patches == 1] = value * model.heart.am_ref[model.heart.patches == 1] | |
| elif par == "swth": | |
| model.heart.vw[model.heart.patches == 2] = value * model.heart.am_ref[model.heart.patches == 2] | |
| elif par == "lawth": | |
| model.heart.vw[model.heart.patches == 3] = value * model.heart.am_ref[model.heart.patches == 3] | |
| elif par == "rawth": | |
| model.heart.vw[model.heart.patches == 4] = value * model.heart.am_ref[model.heart.patches == 4] | |
| # Update total wall volumes and areas to reflect any changes | |
| set_total_wall_volumes_areas(model) | |
| model.heart.v_tot_0 = unloaded_heart_volume(model.heart.am_ref_w, model.heart.vw_w) | |
| def list_change_pars(): | |
| """Return list of parameters that can be changed using change_pars()""" | |
| return ["sbv", "hr", "k_initial", "cvp", "cas", "cap", "cvs", "rvp", "rcs", "ras", "rvs", "rcp", "rap", "rav", | |
| "rmvb", "rtvb", "sact", "sfact", "tad", "td", "tr", "c1", "c3", "c4", "tact", "sact_a", "sfact_a", "tad_a", | |
| "td_a", "tr_a", "c1_a", "c3_a", "c4_a", "tact_a", "avd", "ivd_lv", "ivd_rv", "wth_p", "c1_p", "c3_p", | |
| "c4_p", "prestretch", "amreflfw", "amrefrfw", "amrefsw", "amrefla", "amrefra", "vlfw", "vrfw", "vsw", | |
| "vla", "vra", "fgmaxf+", "fgmaxf-", "nf+", "nf-", "s50f+", "s50f-", "fgmaxr+", "fgmaxr-", "nr+", "nr-", | |
| "s50r+", "s50r-", "t_mem", "tau_f-", "tau_f_min", "tau_f+", "tau_f_max", "tau_r-", "tau_r+", "tau_r_min", | |
| "tau_r_max", "theta_f_min", "theta_f_max", "theta_r_min", "theta_r_max", "gamma", "lvwv", "amrefrfwratio", | |
| "amrefswratio", "amreflaratio", "amrefraratio", "rfwvratio", "swvratio", "lawvratio", "rawvratio", "lfwth", | |
| "rfwth", "swth", "lawth", "rawth"] | |
| def get_pars(model, pars): | |
| """Return dictionary of values for the parameters in the list pars""" | |
| # Change all list elements to lowercase | |
| par_orig = pars | |
| pars = [par.lower() for par in pars] | |
| values = {} | |
| # Heart parameters, ventricles and atria (with suffix _a) separately | |
| chamber_names = ["", "a"] | |
| i_ventricles = model.heart.patches == 0 | |
| i_atria = model.heart.patches == 3 | |
| i_chambers = [i_ventricles, i_atria] | |
| # Circulation | |
| for par_name in pars: | |
| # Circulation | |
| if par_name == "sbv": | |
| values[par_name] = model.circulation.sbv | |
| elif par_name == "hr": | |
| values[par_name] = model.circulation.hr | |
| elif par_name == "k_initial": | |
| values[par_name] = model.circulation.k | |
| # Capacitances | |
| elif par_name == "cvp": | |
| values[par_name] = model.capacitances.cvp | |
| elif par_name == "cas": | |
| values[par_name] = model.capacitances.cas | |
| elif par_name == "cap": | |
| values[par_name] = model.capacitances.cap | |
| elif par_name == "cvs": | |
| values[par_name] = model.capacitances.cvs | |
| # Resistances | |
| elif par_name == "rvp": | |
| values[par_name] = model.resistances.rvp | |
| elif par_name == "rcs": | |
| values[par_name] = model.resistances.rcs | |
| elif par_name == "ras": | |
| values[par_name] = model.resistances.ras | |
| elif par_name == "rvs": | |
| values[par_name] = model.resistances.rvs | |
| elif par_name == "rcp": | |
| values[par_name] = model.resistances.rcp | |
| elif par_name == "rap": | |
| values[par_name] = model.resistances.rap | |
| elif par_name == "rav": | |
| values[par_name] = model.resistances.rav | |
| elif par_name == "rmvb": | |
| values[par_name] = model.resistances.rmvb | |
| elif par_name == "rtvb": | |
| values[par_name] = model.resistances.rtvb | |
| elif par_name == "sact": | |
| values[par_name] = model.heart.sf_act[i_ventricles==0][0] | |
| elif par_name == "sfact": | |
| values[par_name] = model.heart.sf_act[i_ventricles==0][0] | |
| elif par_name == "tad": | |
| values[par_name] = model.heart.t_ad[i_ventricles==0][0] | |
| elif par_name == "td": | |
| values[par_name] = model.heart.tau_d[i_ventricles==0][0] | |
| elif par_name == "tr": | |
| values[par_name] = model.heart.tau_r[i_ventricles==0][0] | |
| elif par_name == "c1": | |
| values[par_name] = model.heart.c_1[i_ventricles==0][0] | |
| elif par_name == "c3": | |
| values[par_name] = model.heart.c_3[i_ventricles==0][0] | |
| elif par_name == "c4": | |
| values[par_name] = model.heart.c_4[i_ventricles==0][0] | |
| elif par_name == "tact": | |
| values[par_name] = model.heart.t_act[i_ventricles < 3] | |
| elif par_name == "sact_a": | |
| values[par_name] = model.heart.sf_act[i_ventricles==3][0] | |
| elif par_name == "sfact_a": | |
| values[par_name] = model.heart.sf_act[i_ventricles==3][0] | |
| elif par_name == "tad_a": | |
| values[par_name] = model.heart.t_ad[i_ventricles==3][0] | |
| elif par_name == "td_a": | |
| values[par_name] = model.heart.tau_d[i_ventricles==3][0] | |
| elif par_name == "tr_a": | |
| values[par_name] = model.heart.tau_r[i_ventricles==3][0] | |
| elif par_name == "c1_a": | |
| values[par_name] = model.heart.c_1[i_ventricles==3][0] | |
| elif par_name == "c3_a": | |
| values[par_name] = model.heart.c_3[i_ventricles==3][0] | |
| elif par_name == "c4_a": | |
| values[par_name] = model.heart.c_4[i_ventricles==3][0] | |
| elif par_name == "tact_a": | |
| values[par_name] = model.heart.t_act[i_ventricles >= 3] | |
| # Timing properties, atriaventricular delay and intraventricular delay (between lfw/rfw and septum) | |
| elif par_name == "avd": | |
| values[par_name] = model.heart.t_act[i_ventricles] - model.heart.t_act[i_atria] | |
| elif par_name == "ivd_lv": | |
| values[par_name] = model.heart.t_act[model.heart.patches == 0] - model.heart.t_act[model.heart.patches == 2] | |
| elif par_name == "ivd_rv": | |
| values[par_name] = model.heart.t_act[model.heart.patches == 1] - model.heart.t_act[model.heart.patches == 2] | |
| # Pericardium | |
| elif par_name == "wth_p": | |
| values[par_name] = model.pericardium.thickness | |
| elif par_name == "c1_p": | |
| values[par_name] = model.pericardium.c_1 | |
| elif par_name == "c3_p": | |
| values[par_name] = model.pericardium.c_3 | |
| elif par_name == "c4_p": | |
| values[par_name] = model.pericardium.c_4 | |
| elif par_name == "prestretch": | |
| values[par_name] = model.pericardium.pre_stretch | |
| # Heart area - maintain ratio of AmRefs within each wall but scale according to total AmRef given | |
| elif par_name == "amreflfw": | |
| values[par_name] = np.sum(model.heart.am_ref[model.heart.patches == 0]) | |
| elif par_name == "amrefrfw": | |
| values[par_name] = np.sum(model.heart.am_ref[model.heart.patches == 1]) | |
| elif par_name == "amrefsw": | |
| values[par_name] = np.sum(model.heart.am_ref[model.heart.patches == 2]) | |
| elif par_name == "amrefla": | |
| values[par_name] = np.sum(model.heart.am_ref[model.heart.patches == 3]) | |
| elif par_name == "amrefra": | |
| values[par_name] = np.sum(model.heart.am_ref[model.heart.patches == 4]) | |
| # Wall volume, maintain current ratio in wall volumes between patches | |
| elif par_name == "vlfw": | |
| values[par_name] = np.sum(model.heart.vw[model.heart.patches == 0]) | |
| elif par_name == "vrfw": | |
| values[par_name] = np.sum(model.heart.vw[model.heart.patches == 1]) | |
| elif par_name == "vsw": | |
| values[par_name] = np.sum(model.heart.vw[model.heart.patches == 2]) | |
| elif par_name == "vla": | |
| values[par_name] = np.sum(model.heart.vw[model.heart.patches == 3]) | |
| elif par_name == "vra": | |
| values[par_name] = np.sum(model.heart.vw[model.heart.patches == 4]) | |
| # Sigmoid growth parameters | |
| elif par_name == "fgmaxf+": | |
| values[par_name] = model.growth.fgmax_f_plus | |
| elif par_name == "fgmaxf-": | |
| values[par_name] = model.growth.fgmax_f_min | |
| elif par_name == "nf+": | |
| values[par_name] = model.growth.n_f_plus | |
| elif par_name == "nf-": | |
| values[par_name] = model.growth.n_f_min | |
| elif par_name == "s50f+": | |
| values[par_name] = model.growth.s50_f_plus | |
| elif par_name == "s50f-": | |
| values[par_name] = model.growth.s50_f_min | |
| elif par_name == "fgmaxr+": | |
| values[par_name] = model.growth.fgmax_r_plus | |
| elif par_name == "fgmaxr-": | |
| values[par_name] = model.growth.fgmax_r_min | |
| elif par_name == "nr+": | |
| values[par_name] = model.growth.n_r_plus | |
| elif par_name == "nr-": | |
| values[par_name] = model.growth.n_r_min | |
| elif par_name == "s50r+": | |
| values[par_name] = model.growth.s50_r_plus | |
| elif par_name == "s50r-": | |
| values[par_name] = model.growth.s50_r_min | |
| elif par_name == "t_mem": | |
| values[par_name] = model.growth.t_mem | |
| # Kuhl growth parameters | |
| elif par_name == "tau_f-": | |
| values[par_name] = model.growth.tau_f_min | |
| elif par_name == "tau_f+": | |
| values[par_name] = model.growth.tau_f_plus | |
| elif par_name == "tau_f+": | |
| values[par_name] = model.growth.tau_f_plus | |
| elif par_name == "tau_r-": | |
| values[par_name] = model.growth.tau_r_min | |
| elif par_name == "tau_r+": | |
| values[par_name] = model.growth.tau_r_plus | |
| elif par_name == "theta_f_min": | |
| values[par_name] = model.growth.theta_f_min | |
| elif par_name == "theta_f_max": | |
| values[par_name] = model.growth.theta_f_max | |
| elif par_name == "theta_r_min": | |
| values[par_name] = model.growth.theta_r_min | |
| elif par_name == "theta_r_max": | |
| values[par_name] = model.growth.theta_r_max | |
| elif par_name == "gamma": | |
| values[par_name] = model.growth.gamma | |
| else: | |
| print(f"Parameter {par_name} not found in model") | |
| values[par_name] = np.nan | |
| # Restore original case of parameter names | |
| values = {par_orig[i]: value for i, (key, value) in enumerate(values.items())} | |
| # Convert all values to float | |
| values = {key: float(value) for key, value in values.items()} | |
| return values | |