|
|
| import numpy as np
|
| import string, sys, getopt
|
|
|
| DB_DIR = "/home/krypton/projects/TrR_for_design"
|
|
|
|
|
| AA_COMP = np.array([0.07892653, 0.04979037, 0.0451488 , 0.0603382 , 0.01261332,
|
| 0.03783883, 0.06592534, 0.07122109, 0.02324815, 0.05647807,
|
| 0.09311339, 0.05980368, 0.02072943, 0.04145316, 0.04631926,
|
| 0.06123779, 0.0547427 , 0.01489194, 0.03705282, 0.0691271])
|
|
|
|
|
|
|
| AA_REF = np.array([-1.31161863, -0.44993051, 0.06198913, -0.81825899, 2.63941964,
|
| 0.44087343, -0.93833546, -0.7374156 , 1.54108622, -0.92757075,
|
| -1.70878817, -0.9461753 , 1.77794612, 0.2156388 , 0.3293717 ,
|
| -1.012154 , -0.60176806, 2.99381739, 0.84557686, -1.02749264])
|
|
|
| alpha_1 = list("ARNDCQEGHILKMFPSTWYV-")
|
| states = len(alpha_1)
|
| alpha_3 = ['ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY','HIS','ILE',
|
| 'LEU','LYS','MET','PHE','PRO','SER','THR','TRP','TYR','VAL','GAP']
|
|
|
| aa_1_N = {a:n for n,a in enumerate(alpha_1)}
|
| aa_3_N = {a:n for n,a in enumerate(alpha_3)}
|
| aa_N_1 = {n:a for n,a in enumerate(alpha_1)}
|
| aa_1_3 = {a:b for a,b in zip(alpha_1,alpha_3)}
|
| aa_3_1 = {b:a for a,b in zip(alpha_1,alpha_3)}
|
|
|
| def AA_to_N(x):
|
|
|
| x = np.array(x);
|
| if x.ndim == 0: x = x[None]
|
| return [[aa_1_N.get(a, states-1) for a in y] for y in x]
|
|
|
| def N_to_AA(x):
|
|
|
| x = np.array(x);
|
| if x.ndim == 1: x = x[None]
|
| return ["".join([aa_N_1.get(a,"-") for a in y]) for y in x]
|
|
|
| def parse_PDB(x, atoms=['N','CA','C'], chain=None):
|
| '''
|
| input: x = PDB filename
|
| atoms = atoms to extract (optional)
|
| output: (length, atoms, coords=(x,y,z)), sequence
|
| '''
|
| xyz,seq,min_resn,max_resn = {},{},np.inf,-np.inf
|
| for line in open(x,"rb"):
|
| line = line.decode("utf-8","ignore").rstrip()
|
|
|
| if line[:6] == "HETATM" and line[17:17+3] == "MSE":
|
| line = line.replace("HETATM","ATOM ")
|
| line = line.replace("MSE","MET")
|
|
|
| if line[:4] == "ATOM":
|
| ch = line[21:22]
|
| if ch == chain or chain is None:
|
| atom = line[12:12+4].strip()
|
| resi = line[17:17+3]
|
| resn = line[22:22+5].strip()
|
| x,y,z = [float(line[i:(i+8)]) for i in [30,38,46]]
|
|
|
| if resn[-1].isalpha(): resa,resn = resn[-1],int(resn[:-1])-1
|
| else: resa,resn = "",int(resn)-1
|
| if resn < min_resn: min_resn = resn
|
| if resn > max_resn: max_resn = resn
|
| if resn not in xyz: xyz[resn] = {}
|
| if resa not in xyz[resn]: xyz[resn][resa] = {}
|
| if resn not in seq: seq[resn] = {}
|
| if resa not in seq[resn]: seq[resn][resa] = resi
|
|
|
| if atom not in xyz[resn][resa]:
|
| xyz[resn][resa][atom] = np.array([x,y,z])
|
|
|
|
|
| seq_,xyz_ = [],[]
|
| for resn in range(min_resn,max_resn+1):
|
| if resn in seq:
|
| for k in sorted(seq[resn]): seq_.append(aa_3_N.get(seq[resn][k],20))
|
| else: seq_.append(20)
|
| if resn in xyz:
|
| for k in sorted(xyz[resn]):
|
| for atom in atoms:
|
| if atom in xyz[resn][k]: xyz_.append(xyz[resn][k][atom])
|
| else: xyz_.append(np.full(3,np.nan))
|
| else:
|
| for atom in atoms: xyz_.append(np.full(3,np.nan))
|
| return np.array(xyz_).reshape(-1,len(atoms),3), np.array(seq_)
|
|
|
| def extend(a,b,c, L,A,D):
|
| '''
|
| input: 3 coords (a,b,c), (L)ength, (A)ngle, and (D)ihedral
|
| output: 4th coord
|
| '''
|
| N = lambda x: x/np.sqrt(np.square(x).sum(-1,keepdims=True) + 1e-8)
|
| bc = N(b-c)
|
| n = N(np.cross(b-a, bc))
|
| m = [bc,np.cross(n,bc),n]
|
| d = [L*np.cos(A), L*np.sin(A)*np.cos(D), -L*np.sin(A)*np.sin(D)]
|
| return c + sum([m*d for m,d in zip(m,d)])
|
|
|
| def to_len(a,b):
|
| '''given coordinates a-b, return length or distance'''
|
| return np.sqrt(np.sum(np.square(a-b),axis=-1))
|
|
|
| def to_len_pw(a,b=None):
|
| '''given coordinates a-b return pairwise distance matrix'''
|
| a_norm = np.square(a).sum(-1)
|
| if b is None: b,b_norm = a,a_norm
|
| else: b_norm = np.square(b).sum(-1)
|
| return np.sqrt(np.abs(a_norm.reshape(-1,1) + b_norm - 2*(a@b.T)))
|
|
|
| def to_ang(a,b,c):
|
| '''given coordinates a-b-c, return angle'''
|
| D = lambda x,y: np.sum(x*y,axis=-1)
|
| N = lambda x: x/np.sqrt(np.square(x).sum(-1,keepdims=True) + 1e-8)
|
| return np.arccos(D(N(b-a),N(b-c)))
|
|
|
| def to_dih(a,b,c,d):
|
| '''given coordinates a-b-c-d, return dihedral'''
|
| D = lambda x,y: np.sum(x*y,axis=-1)
|
| N = lambda x: x/np.sqrt(np.square(x).sum(-1,keepdims=True) + 1e-8)
|
| bc = N(b-c)
|
| n1 = np.cross(N(a-b),bc)
|
| n2 = np.cross(bc,N(c-d))
|
| return np.arctan2(D(np.cross(n1,bc),n2),D(n1,n2))
|
|
|
| def prep_input(pdb, chain=None, mask_gaps=False):
|
| '''Parse PDB file and return features compatible with TrRosetta'''
|
| ncac, seq = parse_PDB(pdb,["N","CA","C"], chain=chain)
|
|
|
|
|
| if mask_gaps:
|
| mask = seq != 20
|
| ncac, seq = ncac[mask], seq[mask]
|
|
|
| N,CA,C = ncac[:,0], ncac[:,1], ncac[:,2]
|
| CB = extend(C, N, CA, 1.522, 1.927, -2.143)
|
|
|
| dist_ref = to_len(CB[:,None], CB[None,:])
|
| omega_ref = to_dih(CA[:,None], CB[:,None], CB[None,:], CA[None,:])
|
| theta_ref = to_dih( N[:,None], CA[:,None], CB[:,None], CB[None,:])
|
| phi_ref = to_ang(CA[:,None], CB[:,None], CB[None,:])
|
|
|
| def mtx2bins(x_ref, start, end, nbins, mask):
|
| bins = np.linspace(start, end, nbins)
|
| x_true = np.digitize(x_ref, bins).astype(np.uint8)
|
| x_true[mask] = 0
|
| return np.eye(nbins+1)[x_true][...,:-1]
|
|
|
| p_dist = mtx2bins(dist_ref, 2.0, 20.0, 37, mask=(dist_ref > 20))
|
| p_omega = mtx2bins(omega_ref, -np.pi, np.pi, 25, mask=(p_dist[...,0]==1))
|
| p_theta = mtx2bins(theta_ref, -np.pi, np.pi, 25, mask=(p_dist[...,0]==1))
|
| p_phi = mtx2bins(phi_ref, 0.0, np.pi, 13, mask=(p_dist[...,0]==1))
|
| feat = np.concatenate([p_theta, p_phi, p_dist, p_omega],-1)
|
| return {"seq":N_to_AA(seq), "feat":feat, "dist_ref":dist_ref}
|
|
|
| def split_feat(feat):
|
| out = {}
|
| for k,i,j in [["theta",0,25],["phi",25,38],["dist",38,75],["omega",75,100]]:
|
| out[k] = feat[...,i:j]
|
| return out
|
|
|
| def pairwise_id(x):
|
| '''get pairwise sequence identity'''
|
| x = np.array(x)
|
| return (x[:,None] == x[None,:]).mean(-1)
|
|
|
| def arr2str(x, d=3):
|
| return np.array2string(x,formatter={'float_kind':lambda x: f"%.{d}f" % x}).replace("\n","").replace(" ",",")
|
|
|
|
|
|
|
|
|
|
|
| def parse_fasta(filename, a3m=False):
|
| '''function to parse fasta file'''
|
| if a3m:
|
|
|
|
|
| rm_lc = str.maketrans(dict.fromkeys(string.ascii_lowercase))
|
| header, sequence = [],[]
|
| lines = open(filename, "r")
|
| for line in lines:
|
| line = line.rstrip()
|
| if len(line) > 0:
|
| if line[0] == ">":
|
| header.append(line[1:])
|
| sequence.append([])
|
| else:
|
| if a3m: line = line.translate(rm_lc)
|
| else: line = line.upper()
|
| sequence[-1].append(line)
|
| lines.close()
|
| sequence = [''.join(seq) for seq in sequence]
|
| return header, sequence
|
|
|
| def mk_msa(seqs):
|
| '''one hot encode msa'''
|
| alphabet = list("ARNDCQEGHILKMFPSTWYV-")
|
| states = len(alphabet)
|
|
|
| alpha = np.array(alphabet, dtype='|S1').view(np.uint8)
|
| msa = np.array([list(s) for s in seqs], dtype='|S1').view(np.uint8)
|
| for n in range(states):
|
| msa[msa == alpha[n]] = n
|
| msa[msa > states] = states-1
|
|
|
| return np.eye(states)[msa]
|
|
|
| def get_dist_acc(pred, true, true_mask=None,sep=5,eps=1e-8):
|
|
|
| pred,true = [x[...,39:51].sum(-1) for x in[pred,true]]
|
| if true_mask is not None:
|
| mask = true_mask[:,:,None] * true_mask[:,None,:]
|
| else: mask = np.ones_like(pred)
|
| i,j = np.triu_indices(pred.shape[-1],k=sep)
|
| P,T,M = pred[...,i,j], true[...,i,j], mask[...,i,j]
|
|
|
| pos = (T*P*M).sum(-1)/((M*T).sum(-1)+eps)
|
| neg = ((1-T)*(1-P)*M).sum(-1)/((M*(1-T)).sum(-1)+eps)
|
| return 2.0*(pos*neg)/(pos+neg+eps)
|
|
|
| def inv_cov(Y):
|
| '''given MSA, return contacts'''
|
|
|
| N,L = Y.shape
|
| K = Y.max()+1
|
| Y = np.eye(K)[Y]
|
|
|
|
|
| Y_flat = Y.reshape(N,-1)
|
|
|
|
|
| c = np.cov(Y_flat.T)
|
|
|
| shrink = 4.5/np.sqrt(N) * np.eye(c.shape[0])
|
|
|
| ic = np.linalg.inv(c + shrink)
|
|
|
| ic = ic.reshape(L,K,L,K)
|
|
|
|
|
| ic_norm = np.sqrt(np.square(ic).sum((1,3)))
|
| np.fill_diagonal(ic_norm,0)
|
|
|
|
|
| ap = ic_norm.sum(0)
|
| apc = ic_norm - (ap[:,None]*ap[None,:])/ap.sum()
|
| np.fill_diagonal(apc,0.0)
|
| return apc
|
|
|
| def to_dict(label, var_list):
|
| return dict(zip(label,var_list))
|
|
|
| def to_list(label, var_dict, default=None):
|
| return [var_dict.get(k, default) for k in label]
|
|
|
|
|
| class parse_args:
|
| def __init__(self):
|
| self.long,self.short = [],[]
|
| self.info,self.help = [],[]
|
|
|
| def txt(self,help):
|
| self.help.append(["txt",help])
|
|
|
| def add(self, arg, default, type, help=None):
|
| self.long.append(arg[0])
|
| key = arg[0].replace("=","")
|
| self.info.append({"key":key, "type":type,
|
| "value":default, "arg":[f"--{key}"]})
|
| if len(arg) == 2:
|
| self.short.append(arg[1])
|
| s_key = arg[1].replace(":","")
|
| self.info[-1]["arg"].append(f"-{s_key}")
|
| if help is not None:
|
| self.help.append(["opt",[arg,help]])
|
|
|
| def parse(self,argv):
|
| for opt, arg in getopt.getopt(argv,"".join(self.short),self.long)[0]:
|
| for x in self.info:
|
| if opt in x["arg"]:
|
| if x["type"] is None: x["value"] = (x["value"] == False)
|
| else: x["value"] = x["type"](arg)
|
|
|
| opts = {x["key"]:x["value"] for x in self.info}
|
| print(str(opts).replace(" ",""))
|
| return dict2obj(opts)
|
|
|
| def usage(self, err):
|
| for type,info in self.help:
|
| if type == "txt": print(info)
|
| if type == "opt":
|
| arg, helps = info
|
| help = helps[0]
|
| if len(arg) == 1: print("--%-15s : %s" % (arg[0],help))
|
| if len(arg) == 2: print("--%-10s -%-3s : %s" % (arg[0],arg[1].replace(":",""),help))
|
| for help in helps[1:]: print("%19s %s" % ("",help))
|
| print(f"< {err} >")
|
| print(" "+"-"*(len(err)+2))
|
| print(" \ ^__^ ")
|
| print(" \ (oo)\_______ ")
|
| print(" (__)\ )\/\ ")
|
| print(" ||----w | ")
|
| print(" || || ")
|
| sys.exit()
|
|
|
| class dict2obj():
|
| def __init__(self, dictionary):
|
| for key in dictionary:
|
| setattr(self, key, dictionary[key])
|
|
|