# load libraries import numpy as np import string, sys, getopt DB_DIR = "/home/krypton/projects/TrR_for_design" # location of databases # ivan's natural AA composition 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]) # David Juergens' optimized AA reference weights # /home/norn/DL/200701_ref_weight_optimization/nelder_mead/scripts/nm_filtered/params_140 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): # ["ARND"] -> [[0,1,2,3]] 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): # [[0,1,2,3]] -> ["ARND"] 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]) # convert to numpy arrays, fill in missing values 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) # mask gap regions 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(" ",",") ##################################################################### # Working with multiple sequence alignments ##################################################################### def parse_fasta(filename, a3m=False): '''function to parse fasta file''' if a3m: # for a3m files the lowercase letters are removed # as these do not align to the query sequence 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): ## compute accuracy of CB features ## 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] ## give equal weighting to positive and negative predictions 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] # flatten msa (N,L,A) -> (N,L*A) Y_flat = Y.reshape(N,-1) # compute covariance matrix (L*A,L*A) c = np.cov(Y_flat.T) # compute shrinkage (l2 regularization) shrink = 4.5/np.sqrt(N) * np.eye(c.shape[0]) # take the inverse to solve for w ic = np.linalg.inv(c + shrink) # (L,A,L,A) ic = ic.reshape(L,K,L,K) # take l2norm to reduce (L,A,L,A) to (L,L) matrix ic_norm = np.sqrt(np.square(ic).sum((1,3))) np.fill_diagonal(ic_norm,0) #Average product correction (aka remove largest eigenvector) 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 for parsing arguments 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])