Location: FCU_cardiacAP_composite @ 50eec1a67a27 / parameter_finder / find_BG_parameters_composite.py

Author:
Shelley Fong <s.fong@auckland.ac.nz>
Date:
2022-05-05 12:49:23+12:00
Desc:
CaB and Ca_leak updated
Permanent Source URI:
https://models.physiomeproject.org/workspace/839/rawfile/50eec1a67a27837d7537caabc359bc616773938a/parameter_finder/find_BG_parameters_composite.py

# find bond-graph parameters for a system with multiple modules

# require separate folders within this directory containing each module's kinetic_parameters.py file and data files

# write out cellml file in text form

# prints out error between given kinetic parameters, and parameters found back-transforming the bond-graph parameters

import os
import sys
import importlib
import json
import csv
import math
import numpy as np
from scipy.linalg import null_space
import sympy
from sympy import Matrix, S, nsimplify
from fractions import Fraction
import time

def read_IDs(path):
    data = []
    with open(path,'r') as f:
        reader = csv.reader(f)
        for row in reader:
            data.append(row[0])
        f.close()
    return data

def load_matrix(stoich_path):
    matrix = []
    with open(stoich_path,'r') as f:
        reader = csv.reader(f,delimiter=',')
        for row in reader:
            matrix.append([float(r) for r in row])
        f.close()
    return matrix

# def rational_nullspace(A, max_denom = 10):
    # v = null_space(A)
    # vFrac = [[Fraction(num).limit_denominator(max_denominator=max_denom) for num in row] for row in v]

    # vRat = [] #np.zeros([len(vFrac),len(vFrac[0])])
    # if not v.any():
        # return []
    # for row in vFrac:
        # largest_denom = max([res.denominator for res in row])
        # vRat.append( [vi.numerator for vi in row] )
    # return vRat

def calcT(I_vec,num_rows):
    num_cols = len(I_vec)
    T = np.zeros([num_rows,num_cols])
    for i in range(num_cols):
        T[I_vec[i]][i] = 1
    
    return T

if __name__ == "__main__":

    tStart = time.time()

    # Set directories
    current_dir = os.getcwd()
    main_dir = os.path.dirname(current_dir)
    output_dir = current_dir + '\output'
    whole_name = main_dir.split('\\')[-1]
    if not os.path.exists(output_dir):
        os.mkdir(output_dir)

    R = 8.314  # unit    J / mol / K
    T = 310
    F = 96485

    ion_names = ['Na','Ca','K']
    ion_names = [i+'_o' for i in ion_names] + [i+'_i' for i in ion_names] + [i+'_SR' for i in ion_names]
    ion_names += ['H','MgATP','MgADP','Pi']

    # define constants and volumes
    cNao = 140  # unit    mM
    cNai = 10  # unit    mM
    cKo = 5.4
    cKi = 145
    cCai = 0.00012
    cCa_di = cCai; #1.7374E-04 # mM
    cCa_sr = 5.5545E-01 # mM
    N_A = 6.022e23
    A_cap = 2*1.1e-5 # assume same membrane folding as LRd #1.534e-4  # Unit    cm ^ 2
    Cm = 60e-6 # Unit microF     # pCa uses this
    V_myo = 3.71  # pL, for Kernik iPSCell
    V_o = 0.12*V_myo/0.88 # Using cleft volume calc from LRd
    V_di = V_myo * 0.0539 # diadic volume
    V_sr = V_myo * 0.06  # SR volume
    V_isr = V_myo + V_sr  # Intracellular volume + SR volume

    V = dict()
    V['V_myo'] = V_myo
    V['V_o'] = V_o
    V['V_di'] = V_di
    V['V_SR'] = V_sr
    V['V_ISR'] = V_isr
    V['F'] = F
    V['R'] = R
    V['T'] = T
    V['Cm'] = Cm
    V['N_A'] = N_A
    V['A_cap'] = A_cap
    V['cNao'] = cNao
    V['cNai'] = cNai
    V['cKi'] = cKi
    V['cKo'] = cKo
    V['c_di'] = cCa_di
    V['c_sr'] = cCa_sr

    # store channel densities as 1/um2
    chDensity = dict()
    chDensity['fast_Na'] = 16
    chDensity['K1'] = 0.555
    chDensity['K'] = 0.7
    chDensity['Kp'] = 0.095
    chDensity['LCC'] = 6.5
    chDensity['RyR'] = 6.5
    # the following values are guesses
    chDensity['Kr'] = 0.35
    chDensity['Ks'] = 0.35
    chDensity['TCC'] = 15
    chDensity['TO'] = 0.1
    chDensity['ns'] = 0.1
    chDensity['K_ATP'] = 0.1
    chDensity['funny'] = 0.1
    chDensity['CaB'] = 0.1
    chDensity['NaB'] = 0.1
    chDensity['pCa'] = 0.1
    chDensity['SERCA'] = 1e-8

    ## Load stoichiometric matrices and kinetic rate constants
    exclude_folders = ['.git', 'exposure','parameter_finder','python','units_and_constants','FCU_cardiacAP_project','old_models']
    subsystem_names = ['individual']
    Pan_channel_names = ['K1','Kp','K','fast_Na','LCC','NaK','NCX','CMDN_buffer','TRPN_buffer']
    if False: # Clancy01 channels
        subsystem_names = next(os.walk(main_dir))[1]
        exclude_folders2 = exclude_folders.copy()
        # exclude_folders2.append('funny')
        # exclude_folders2.append('K')
        # exclude_folders2.append('SERCA')
        # exclude_folders2.append('RyR')
        # exclude_folders2.append('Ca_leak')
        # exclude_folders2.append('pCa')
        # exclude_folders.append('pCa') # pCa causes param_error to SPIKE
        # exclude_folders.append('TRPN_buffer')
        # exclude_folders.append('CMDN_buffer')
        subsystem_names = [s for s in subsystem_names if s not in exclude_folders2]
    else:
        # Pan channels only, with slight new additions
        subsystem_names = ['K1','fast_Na','LCC','NaK','NCX','CMDN_buffer','TRPN_buffer', # Kp
                           'Kr','Ks','NaB','CaB','funny','pCa','Ca_leak','SERCA','RyR',
                           'TCC'] #
    subsystem_names.sort()
    num_subsystems = len(subsystem_names)
    sys_struct = {c:{} for c in subsystem_names}
    rxnIDs = []
    Knames = []
    znames = []
    zvals = []
    Kname_modules = dict()
    for i_system in range(num_subsystems):
        forward_mat_path = 'data\\all_forward_matrix.txt'
        sys_name = subsystem_names[i_system]
        sys_dir = main_dir + '\\' + sys_name +'\parameter_finder\\'
        os.chdir(sys_dir)

        reverse_mat_path = 'data\\all_reverse_matrix.txt'
        N_f = load_matrix(forward_mat_path)
        N_r = load_matrix(reverse_mat_path)
        sys_struct[sys_name]['N_f'] = N_f
        sys_struct[sys_name]['N_r'] = N_r
        
        # print(subsystem_names[i_system])
        dims = dict()
        dims['num_rows'] = len(N_f)
        dims['num_cols'] = len(N_f[0])
        I = np.identity(dims['num_cols'])
        M = np.append(np.append(I, np.transpose(N_f),1), np.append(I, np.transpose(N_r),1),0)
        sys.path.append(sys_dir)
        try:
            V['numChannels'] = chDensity[sys_name]*V['A_cap']*1e8 # convert to um2
            print('Num channels for ',sys_name,' is', V['numChannels'])
            print ('\t\tfmol is', 1e15*V['numChannels']/N_A)
        except:
            print('No channel density for ',sys_name)
        globals()['kp_' + sys_name] = importlib.import_module('kinetic_parameters_' + sys_name)
        [k_kinetic, N_cT, K_C, W] = globals()['kp_' + sys_name].kinetic_parameters(M,True,dims, V)
        sys_struct[sys_name]['kfkr'] = k_kinetic
        sys_struct[sys_name]['Kc'] = K_C
        sys_struct[sys_name]['N_cT'] = N_cT
        sys_struct[sys_name]['W'] = W[dims['num_cols']:]
        rxnID = read_IDs('data\\rxnID.txt')
        rxnIDs.extend(rxnID)
        sys_struct[sys_name]['rxnID'] = rxnID
        Kname = read_IDs('data\\Kname.txt')
        Knames.extend(Kname)
        sys_struct[sys_name]['Kname'] = Kname
        try:
            zname = read_IDs('data\\zname.txt')
            zval = read_IDs('data\\z_value.txt')
            for iz,z in enumerate(zname):
                if z not in znames:
                    znames.append(z)
                    zvals.append(zval[iz])
        except:
            pass

    Kunique = []
    keep_Kunique_IDs = []
    Krepeats = []
    for ii, ik in enumerate(Knames):
        # if ~any(strcmp(Kunique,ik)):
        if ik not in Kunique:
            Kunique.append(ik)
            keep_Kunique_IDs.append(ii)
        else:
            Krepeats.append(ik)
        # id = Kunique.index(ik)
        # keep_Kunique_IDs.append(id)
    kapparepeats = []
    kappaunique = []
    for ik in rxnIDs:
        if ik not in kappaunique:
            kappaunique.append(ik)
        else:
            kapparepeats.append(ik)

    os.chdir(current_dir)
    # relations between submodule to whole module

    for name in subsystem_names:
        ids = [Kunique.index(kid) for kid in sys_struct[name]['Kname']]
        sys_struct[name]['I_vec'] = ids
    num_rows = len(Kunique) #max(sys_struct[subsystem_names[-1]]['I_vec'])+1

    N_f = []
    N_r = []

    for sys_name in subsystem_names:
        print(sys_name)
        T = calcT(sys_struct[sys_name]['I_vec'],num_rows)
        sys_struct[sys_name]['T'] = T

        new_f = np.matmul(T,sys_struct[sys_name]['N_f'])
        new_r = np.matmul(T,sys_struct[sys_name]['N_r'])

        if not len(N_f):
            N_f = new_f
            N_r = new_r
        else:
            N_f = np.append(N_f, new_f,1)
            N_r = np.append(N_r, new_r,1)

    N_fT = np.transpose(N_f)
    N_rT = np.transpose(N_r)

    N = N_r - N_f
    N_T = N_rT - N_fT

    num_cols = len(N[0])
    I = np.identity(num_cols)

    M = np.append(np.append(I, N_fT,1), np.append(I, N_rT,1),0)
    # M_rref = sympy.Matrix(M).rref()

    ## Set up the vectors for kinetic rate constants

    kf = []
    kr = []
    W = []
    for sys_name in subsystem_names:
        nrx = int(len(sys_struct[sys_name]['kfkr'])/2)
        kf.extend(sys_struct[sys_name]['kfkr'][:nrx])
        kr.extend(sys_struct[sys_name]['kfkr'][nrx:])
        W.extend(sys_struct[sys_name]['W'])
    k_kinetic = kf +kr
    # retain only the elements of W corresponding to Kunique
    W = [W[ik] for ik in keep_Kunique_IDs]
    W = [1]*num_cols + W
    # W = list(np.append([1]*len(N[0]), [V_myo]*num_rows)) #  THIS IS WRONG

    lambda_expo = np.matmul(np.linalg.pinv(M), [math.log(k) for k in k_kinetic])
    lambdaW = [math.exp(l) for l in lambda_expo]
    lambdak = [lambdaW[i]/W[i] for i in range(len(W))]
    kappa = lambdak[:len(N[0])]
    K = lambdak[len(N[0]):]

    file = open(output_dir + '/all_parameters_out.json', 'w')
    data = { "K": K, "kappa": kappa, "k_kinetic": k_kinetic }
    json.dump(data, file)

    # Checks
    if False:
        N_rref = sympy.Matrix(N).rref()
        R = nsimplify(Matrix(N), rational=True).nullspace() #rational_nullspace(N, max_denom=len(N[0]))
        if R:
            R = np.transpose(np.array(R).astype(np.float64))[0]
        if False:
            # Check that there is a detailed balance constraint
            Z = nsimplify(Matrix(M), rational=True).nullspace() #rational_nullspace(M, 2)
            if Z:
                Z = np.transpose(np.array(Z).astype(np.float64))[0]

    k_est = np.matmul(M,[math.log(k) for k in lambdaW])
    k_est = [math.exp(k) for k in k_est]
    diff = [(k_kinetic[i] - k_est[i])/k_kinetic[i] for i in range(len(k_kinetic))]
    error = np.sum([abs(d) for d in diff])

    K_eq = [kf[i]/kr[i] for i in range(len(kr))]
    
    try:
        zero_est = np.matmul(np.transpose(R),K_eq)
        zero_est_log = np.matmul(np.transpose(R),[math.log(k) for k in K_eq])
    except:
        print('undefined R nullspace')

    if True:
    # list of all K and kappa in directory
        all_subsystem_names = next(os.walk(main_dir))[1]
        all_exclude_folders = exclude_folders #+ ['K']
        all_subsystem_names = [s for s in all_subsystem_names if s not in all_exclude_folders]
        rem_rxnIDs = []
        rem_Knames = []
        all_Kname = []
        all_rxnID = []

        for sname in all_subsystem_names:
            sys_dir = main_dir + '\\' + sname +'\parameter_finder\\'
            rxnID = read_IDs(sys_dir + 'data\\rxnID.txt')
            Kname = read_IDs(sys_dir + 'data\\Kname.txt')
            for k in Kname:
                if (k not in Kunique) and (k not in rem_Knames):
                    rem_Knames.append(k)
            for k in rxnID:
                if k not in rxnIDs:
                    rem_rxnIDs.append(k)

        #     all_Kname.append(Kname)
        #     all_rxnID.append(rxnID)
        # all_Kname = []
        # all_kappaunique = []
        # for ii, ik in enumerate(all_Kname):
        #     if ik not in all_Kunique:
        #         all_Kunique.append(ik)
        # for ik in all_rxnID:
        #     if ik not in all_kappaunique:
        #         all_kappaunique.append(ik)

    # ### print outputs ###
    # include print of remaining kappa/K that are not included: make them zero
    Kkappa_values = {c:1e-6 for c in Kunique+rxnIDs}

    for ik in range(len(kappa)):
        print('var kappa_%s: fmol_per_sec {init: %g, pub: out};' %(rxnIDs[ik],kappa[ik]))
    for r in rem_rxnIDs:
        print('var kappa_%s: fmol_per_sec {init: 0, pub: out};' %(r))
    for ik in range(len(Kunique)):
        print('var K_%s: per_fmol {init: %g, pub: out};' %(Kunique[ik],K[ik]))
    for k in rem_Knames:
        print('var K_%s: per_fmol {init: 1e-6, pub: out};' %(k))
    print('\n')
    for ik in range(len(znames)):
        print('var %s: dimensionless {init: %s, pub: out};' % (znames[ik], zvals[ik]))
    # print moles of gate particles if included in system
    print('\n')
    for sys_name in subsystem_names:
        # if sys_name not in Pan_channel_names:
        gate_count = 0
        for ig in sys_struct[sys_name]['Kname']:
            if ig not in ion_names:
                gate_count += 1
        if gate_count > 0 and sys_name in chDensity.keys():
            for ig in sys_struct[sys_name]['Kname']:
                gate_mol = (1e15*chDensity[sys_name]*V['A_cap']*1e8/N_A)/gate_count
                if ig not in ion_names:
                    print('var q_%s: fmol {init: %g, pub: out};' %(ig, gate_mol))
            if False:
                for ig in sys_struct[sys_name]['Kname']:
                    if ig not in ion_names:
                        print('vars q_%s and q_%s;' %(ig, ig))
                print('\n')

    print('error = ', error)

    # initialise struct for storing modules contributing to a given K
    for ik in range(len(Kunique)):
        Kname_modules[Kunique[ik]] = []

    for sys_name in subsystem_names:
        modKname = sys_struct[sys_name]['Kname']
        for ik in range(len(modKname)):
            Kname_modules[modKname[ik]].append(sys_name)

    # write out CellML code
    if True:
        cellmlfilepath = output_dir + '\\TEMP.cellml.txt'
        with open(cellmlfilepath, 'w') as cid:

            cid.write('def model %s as\n def import using "units_and_constants/units_BG.cellml" for\n\
            unit mM using unit mM;\nunit fmol using unit fmol;\nunit per_fmol using unit per_fmol;\n\
            unit J_per_mol using unit J_per_mol;\nunit fmol_per_sec using unit fmol_per_sec;\n\
            unit C_per_mol using unit C_per_mol;\n  unit J_per_C using unit J_per_C;\n\
            unit microm3 using unit microm3;\n  unit fF using unit fF;\n\
            unit fC using unit fC;\n  unit fA using unit fA;\n\
            unit per_second using unit per_second;\n  unit millivolt using unit millivolt;\n\
            unit per_sec using unit per_sec;\n  unit J_per_K_per_mol using unit J_per_K_per_mol;\n\
            unit fmol_per_L using unit fmol_per_L;\n  unit fmol_per_L_per_sec using unit fmol_per_L_per_sec;\n\
            unit per_sec_per_fmol_per_L using unit per_sec_per_fmol_per_L;\n  unit uM using unit uM;\n\
            unit mM_per_sec using unit mM_per_sec;\n  unit uM_per_sec using unit uM_per_sec;\n\
            unit pL using unit pL;\n  unit m_to_u using unit m_to_u;\n enddef;\n' %(whole_name))
            cid.write('def import using "units_and_constants/constants_BG.cellml" for\n\
                comp constants using comp constants;\nenddef;\n\n')
            for module in subsystem_names:
                cid.write('def import using "%s/BG_%s.cellml" for\ncomp %s using comp %s;\nenddef;\n' % (
                module, module, module, module))
            cid.write('\ndef comp BG_parameters as\n')
            for ik in range(len(kappa)):
                cid.write('var kappa_%s: fmol_per_sec {init: %g, pub: out};\n' % (rxnIDs[ik], kappa[ik]))
            for ik in range(len(Kunique)):
                cid.write('var K_%s: per_fmol {init: %g, pub: out};\n' % (Kunique[ik], K[ik]))
            cid.write('enddef;\n')
            cid.write('    def comp environment as\n\
                var t: second {pub: out};\n\
                var vol_myo: pL {init: %g, pub: out};\n\
                var freq: dimensionless {init: 500};\n' %V_myo)
            for j in range(len(K)):
                cid.write('var q_%s: fmol {init: 1e-888, pub: out};\n' % Kunique[j])

            for module in subsystem_names:
                modRx = sys_struct[module]['rxnID']
                cid.write('\n// %s imports\n' % module)
                for j in modRx:
                    cid.write('var v_%s: fmol_per_sec {pub: in};\n' % (j))
                cid.write('\n')
            cid.write('\n')
            for kun in Kunique:
                cid.write('ode(q_%s, time) =' % (kun))
                for mod in Kname_modules[kun]:
                    cid.write(' + v_m%s ' % (mod))
                cid.write(';\n')
            cid.write('enddef;\n')

            cid.write('\n')
            for module in subsystem_names:
                modKname = sys_struct[module]['Kname']
                modRx = sys_struct[module]['rxnID']
                cid.write('def map between environment and %s for\n' % module)
                cid.write('vars time and time;\n')
                for mod in modKname:
                    cid.write('vars q_%s and q_%s;\n' % (mod, mod))
                for mod in modRx:
                    cid.write('vars v_%s and v_%s;\n' % (mod, mod))
                cid.write('vars q_mem and q_mem;\n')
                cid.write('vars I_mem and q_mem_m%s;\n' %module)
                cid.write('enddef;\n\n')

            for module in subsystem_names:
                modKname = sys_struct[module]['Kname']
                modrxnID = sys_struct[module]['rxnID']
                cid.write('def map between BG_parameters and %s for\n' % (module))
                for ik in modrxnID:
                    cid.write('vars kappa_%s and kappa_%s;\n' % (ik, ik))
                for mod in modKname:
                    cid.write('vars K_%s and K_%s;\n' % (mod, mod))
                cid.write('enddef;\n')
            cid.write('\n')
            for module in subsystem_names:
                cid.write('def map between constants and %s for\n' % (module))
                cid.write('\tvars R and R;\n\tvars T and T;\nenddef;\n')

            cid.write('\nenddef;\n')
        cid.close()

    elapsed = time.time() - tStart
    print('Time elapsed: ',elapsed)