OSCR

A Disorder-Aware Computational Framework to Identify Structurally Tractable Targets in Proliferative Vitreoretinopathy.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches
  1. [1] § Methods › Evaluation of a Target Protein–SNAIL1 › RFdiffusion-Based Binder Design ↔ rf/examples/diffusion.ipynb, lines 475–514 · score 0.67 · initial_guess, rm aa, ProteinMPNN, soluble, folded, binder
  2. [2] § Methods › Evaluation of a Target Protein–SNAIL1 › RFdiffusion-Based Binder Design ↔ af/examples/RSO.ipynb, lines 257–320 · score 0.60 · initial guess, rm aa, Cysteine, soluble, backbone, sequences
  3. [3] § Methods › Evaluation of a Target Protein–SNAIL1 › RFdiffusion-Based Binder Design ↔ af/examples/RSO.ipynb, lines 767–801 · score 0.55 · multimer model, generated sequences, recycles, RMSD, template, pLDDT
  4. [4] § Results › RFdiffusion-Based Binder Design for SNAIL1 ↔ af/examples/RSO.ipynb, lines 121–231 · score 0.53 · designed sequence, ProteinMPNN sequence, AlphaFold, confidence, pLDDT, score

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 1,098 lines · 38 KB · other · 3 matches

  1. # %% [markdown]
  2. # <a href="https://colab.research.google.com/github/sokrypton/ColabDesign/blob/main/af/examples/RSO.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>
  3. # %% [markdown]
  4. #
  5. #
  6. #
  7. # #Protein Design using Relaxed Sequence Optimization
  8. #
  9. #
  10. # **Scalable protein design using optimization in a relaxed sequence space**
  11. #
  12. #
  13. #
  14. #
  15. # Christopher Frank, Ali Khoshouei, Lara Fuß, Lara Weber Dominik Schiewitz,Zhixuan Zhao, Motoyuki Hattori, Yosta de Stigter, Shihao Feng, Sergey Ovchinnikov and Hendrik Dietz
  16. #
  17. #
  18. # This notebook contains code to run relaxed sequence optimisation for de novo protein design as described in the manuscript. There are additional options to modify the pipeline according to ones needs
  19. #
  20. # We recommend using at least an L4 GPU to run this notebook, as the free T4 GPU struggles with larger proteins
  21. #
  22. # Alternativly a local installation of ColabDesign is strongly recommendet, especially for the design of larger proteins.
  23. #
  24. # For questions feel free to reach out to the authors
  25. # %%
  26. #@title setup
  27. %%time
  28. import os
  29. if not os.path.isdir("params"):
  30. # get code
  31. os.system("pip -q install pyppeteer nest_asyncio")
  32. os.system("pip -q install git+https://github.com/sokrypton/ColabDesign.git")
  33. # for debugging
  34. os.system("ln -s /usr/local/lib/python3.*/dist-packages/colabdesign colabdesign")
  35. # download params
  36. os.system("mkdir params")
  37. os.system("apt-get install aria2 -qq")
  38. os.system("aria2c -q -x 16 https://storage.googleapis.com/alphafold/alphafold_params_2022-12-06.tar")
  39. os.system("tar -xf alphafold_params_2022-12-06.tar -C params")
  40. import warnings
  41. warnings.simplefilter(action='ignore', category=FutureWarning)
  42. import os
  43. from colabdesign import mk_afdesign_model, clear_mem
  44. from colabdesign.mpnn import mk_mpnn_model
  45. from IPython.display import HTML
  46. from google.colab import files
  47. import numpy as np
  48. import requests, time
  49. if not os.path.isfile("TMscore"):
  50. os.system("wget -qnc https://zhanggroup.org/TM-score/TMscore.cpp")
  51. os.system("g++ -static -O3 -ffast-math -lm -o TMscore TMscore.cpp")
  52. def tmscore(x,y):
  53. # pass to TMscore
  54. output = os.popen(f'./TMscore {x} {y}')
  55. # parse outputs
  56. parse_float = lambda x: float(x.split("=")[1].split()[0])
  57. o = {}
  58. for line in output:
  59. line = line.rstrip()
  60. if line.startswith("RMSD"): o["rms"] = parse_float(line)
  61. if line.startswith("TM-score"): o["tms"] = parse_float(line)
  62. if line.startswith("GDT-TS-score"): o["gdt"] = parse_float(line)
  63. return o
  64. import asyncio
  65. import nest_asyncio
  66. from pyppeteer import launch
  67. import base64
  68. # Apply nest_asyncio to enable nested event loops
  69. nest_asyncio.apply()
  70. async def fetch_blob_content(page, blob_url):
  71. blob_to_base64 = """
  72. async (blobUrl) => {
  73. const blob = await fetch(blobUrl).then(r => r.blob());
  74. return new Promise((resolve) => {
  75. const reader = new FileReader();
  76. reader.onloadend = () => resolve(reader.result);
  77. reader.readAsDataURL(blob);
  78. });
  79. }
  80. """
  81. base64_data = await page.evaluate(blob_to_base64, blob_url)
  82. _, encoded = base64_data.split(',', 1)
  83. return base64.b64decode(encoded)
  84. async def extract_pdb_file_download_link_and_content(url):
  85. browser = await launch(headless=True, args=['--no-sandbox', '--disable-setuid-sandbox'])
  86. page = await browser.newPage()
  87. await page.goto(url, {'waitUntil': 'networkidle0'})
  88. elements = await page.querySelectorAll('a.btn.bg-purple')
  89. for element in elements:
  90. href = await page.evaluate('(element) => element.getAttribute("href")', element)
  91. if 'blob:https://esmatlas.com/' in href:
  92. content = await fetch_blob_content(page, href)
  93. await browser.close()
  94. return href, content
  95. await browser.close()
  96. return "No PDB file link found.", None
  97. def esmfold_api(sequence):
  98. url = f'https://esmatlas.com/resources/fold/result?fasta_header=%3Eunnamed&sequence={sequence}'
  99. result = asyncio.get_event_loop().run_until_complete(extract_pdb_file_download_link_and_content(url))
  100. if result[1]:
  101. pdb_str = result[1].decode('utf-8')
  102. return pdb_str
  103. else:
  104. return "Failed to retrieve PDB content."
  105. import jax
  106. import jax.numpy as jnp
  107. from colabdesign.af.alphafold.common import residue_constants
  108. # %%
  109. #@title # Unconditional Generation (Custom)
  110. #@markdown For a given length, generate/hallucinate a protein sequence that AlphaFold thinks folds into a well structured protein (high plddt, low pae, many contacts).
  111. LENGTH = 100 #@param {type:"integer"}
  112. #@markdown With copies you can specify the number of identical sequences design, resulting in homo oligomers. Copies = 1 is the standard, resulting in a monomer
  113. COPIES = 1 #@param ["1", "2", "3", "4", "5", "6", "7", "8"] {type:"raw"}
  114. MODE = "manuscript"
  115. #@markdown Select the losses you want to use. For unconditional generation as reported in the manuscript use all the losses. To increase the diversity of designes remove confidence losses and/or increase the weight of the helix loss.
  116. use_rg_loss = True #@param {type:"boolean"}
  117. #@markdown A too strong rg loss can lead to problems and clashes. Use 0.1 for backbones smaller then 600 AA and 0.01 for larger proteins (0.001 for 1000 AA).
  118. rg_weight = 0.1 #@param {type:"raw"}
  119. use_helix_loss = True #@param {type:"boolean"}
  120. use_con_loss = True #@param {type:"boolean"}
  121. use_confidence_loss = True #@param {type:"boolean"}
  122. #@markdown How many halluicnation iteration you want to perform. The standard in the manuscript is 100.
  123. iters = 50 #@param ["100", "50", "30"] {type:"raw"}
  124. #@markdown Select if you want to use the 'standard" ProteinMPNN weights or the soluble ones. The soluble ones usually result in higher in silico as well as experimental sucess, but will increase the negative net charge of the protein which sould potentially interfer with certain protein design problems. The manuscript settings are soluble MPNN
  125. use_solubleMPNN = True #@param {type:"boolean"}
  126. #@markdown Select this to use an experimental ProteinMPNN loss, also backpropagating through ProteinMPNN. This was not used in the manuscript
  127. use_mpnn_loss = False #@param {type:"boolean"}
  128. #@markdown
  129. def add_rg_loss(self, weight=0.1):
  130. '''add radius of gyration loss'''
  131. def loss_fn(inputs, outputs):
  132. xyz = outputs["structure_module"]
  133. ca = xyz["final_atom_positions"][:,residue_constants.atom_order["CA"]]
  134. if self.protocol == "binder":
  135. ca = ca[-self._binder_len:]
  136. #This uses a scaled version of the rg loss, only looking at every 5th residue
  137. if MODE == "manuscript":
  138. ca = ca[::5]
  139. rg = jnp.sqrt(jnp.square(ca - ca.mean(0)).sum(-1).mean() + 1e-8)
  140. if MODE == "original":
  141. rg_th = 2.38 * ca.shape[0] ** 0.365
  142. rg = jax.nn.elu(rg - rg_th)
  143. return {"rg":rg}
  144. self._callbacks["model"]["loss"].append(loss_fn)
  145. self.opt["weights"]["rg"] = weight
  146. def add_mpnn_loss(self, mpnn=0.1, mpnn_seq=0.0):
  147. '''
  148. add mpnn loss
  149. mpnn = maximize confidence of proteinmpnn
  150. mpnn_seq = push designed sequence to match proteinmpnn logits
  151. '''
  152. self._mpnn = mk_mpnn_model(weights = "soluble" if use_solubleMPNN else "original")
  153. def loss_fn(inputs, outputs, aux, key):
  154. # get structure
  155. atom_idx = tuple(residue_constants.atom_order[k] for k in ["N","CA","C","O"])
  156. I = {"S": inputs["aatype"],
  157. "residue_idx": inputs["residue_index"],
  158. "chain_idx": inputs["asym_id"],
  159. "X": outputs["structure_module"]["final_atom_positions"][:,atom_idx],
  160. "mask": outputs["structure_module"]["final_atom_mask"][:,1],
  161. "lengths": self._lengths,
  162. "key": key}
  163. if "offset" in inputs:
  164. I["offset"] = inputs["offset"]
  165. # set autoregressive mask
  166. L = sum(self._lengths)
  167. if self.protocol == "binder":
  168. I["ar_mask"] = 1 - np.eye(L)
  169. I["ar_mask"][-self._len:,-self._len:] = 0
  170. else:
  171. I["ar_mask"] = np.zeros((L,L))
  172. # get logits
  173. logits = self._mpnn._score(**I)["logits"][:,:20]
  174. if self.protocol == "binder":
  175. logits = logits[-self._len:]
  176. else:
  177. logits = logits[:self._len]
  178. aux["mpnn_logits"] = logits
  179. # compute loss
  180. log_q = jax.nn.log_softmax(logits)
  181. p = inputs["seq"]["hard"]
  182. q = jax.nn.softmax(logits)
  183. losses = {}
  184. losses["mpnn"] = -log_q.max(-1).mean()
  185. losses["mpnn_seq"] = -(p * jax.lax.stop_gradient(log_q)).sum(-1).mean()
  186. return losses
  187. self._callbacks["model"]["loss"].append(loss_fn)
  188. self.opt["weights"]["mpnn"] = mpnn
  189. self.opt["weights"]["mpnn_seq"] = mpnn_seq
  190. clear_mem()
  191. af_model = mk_afdesign_model(protocol="hallucination")
  192. af_model.prep_inputs(length=LENGTH, copies=COPIES)
  193. # add extra losses
  194. if use_mpnn_loss: add_mpnn_loss(af_model)
  195. print("length",af_model._lengths)
  196. print("weights",af_model.opt["weights"])
  197. # %%
  198. #This cell runs the design loop. Run this in a for loop for design of multiple proteins
  199. af_model.restart()
  200. af_model.set_seq(mode=["gumbel","soft"])
  201. if use_rg_loss: add_rg_loss(af_model,rg_weight)
  202. if use_helix_loss : af_model.set_weights(helix=-0.2)
  203. if use_con_loss : af_model.set_weights(con=1.0)
  204. if use_confidence_loss : af_model.set_weights(plddt=0.5, pae=0.5)
  205. print("weights",af_model.opt["weights"])
  206. af_model.design_logits(iters-10)
  207. af_model.design_logits(10, save_best=True)
  208. # %%
  209. #This cell plots and saves the results as a pdb file
  210. af_model.save_pdb(f"{af_model.protocol}.pdb")
  211. af_model.plot_pdb()
  212. # %%
  213. HTML(af_model.animate())
  214. # %%
  215. af_model.get_seqs()
  216. # %%
  217. import pandas as pd
  218. #@title # Designability test
  219. #@markdown Test the designability of the backbone, taking in the backbone, generating sequences with solubleMPNN and predicting the sequence with AF2 in single sequence mode.
  220. #@markdown Use Initial Guess (IG) and All Atom Initialisation (AA) for larger proteins
  221. AA = False #@param {type:"boolean"}
  222. IG = False #@param {type:"boolean"}
  223. #@markdown NOTE: we remove cysteines from all designed proteins. Additionally for large proteins we also exclude methions to reduce the number of internal start codons
  224. def designability_test(af_model_test, mpnn_model_test,
  225. num_seqs=8, sampling_temp=0.1, num_recycles=3,
  226. model_num=4, best_metric="rmsd",
  227. in_pdb="init.pdb", out_pdb="final.pdb",
  228. verbose=False):
  229. alphafold_model = f"model_{model_num}_ptm"
  230. af_model_test.prep_inputs(in_pdb)
  231. af_model_test.restart(rm_aa="C,M")
  232. af_model_test._args["best_metric"] = best_metric
  233. L = sum(af_model_test._lengths)
  234. mpnn_model_test.get_af_inputs(af_model_test)
  235. out = mpnn_model_test.sample(num=num_seqs // 8, batch=8,
  236. temperature=sampling_temp)
  237. af_terms = ["plddt", "ptm", "pae", "rmsd", "dgram_cce"]
  238. for k in af_terms: out[k] = []
  239. for n in range(num_seqs):
  240. seq = out["seq"][n]
  241. af_model_test.predict(seq=seq,
  242. num_recycles=num_recycles,
  243. num_models=1,
  244. verbose=False,
  245. models=alphafold_model)
  246. for k in af_terms: out[k].append(af_model_test.aux["log"][k])
  247. out["pae"][-1] = out["pae"][-1] * 31
  248. af_model_test._save_results(save_best=True, verbose=verbose)
  249. af_model_test._k += 1
  250. af_model_test.save_pdb(out_pdb)
  251. labels = ["score"] + af_terms + ["seq"]
  252. data = [[out[k][n] for k in labels] for n in range(num_seqs)]
  253. labels[0] = "mpnn"
  254. df = pd.DataFrame(data, columns=labels)
  255. return df
  256. af_model_test = mk_afdesign_model(protocol="fixbb",best_metric="rmsd",use_initial_guess=IG,use_initial_atom_pos=AA,use_templates=False)
  257. mpnn_model_test = mk_mpnn_model(weights="soluble")
  258. lowest_rmsd = float('inf')
  259. lowest_rmsd_data = None
  260. in_pdb = f"{af_model.protocol}.pdb"
  261. out_pdb = f"{af_model.protocol}_out.pdb"
  262. out = designability_test(af_model_test, mpnn_model_test,
  263. num_seqs=8, sampling_temp=0.1, num_recycles=3,
  264. model_num=4, best_metric="rmsd",
  265. in_pdb=in_pdb, out_pdb=out_pdb,
  266. verbose=True)
  267. # %%
  268. from colabdesign import mk_afdesign_model, clear_mem
  269. from colabdesign.af.alphafold.common import residue_constants
  270. import jax
  271. import jax.numpy as jnp
  272. #@title # OPTIONAL Unconditional Generation (Manuscript Code)
  273. #@markdown This code generates a sample of 10 unconditional proteins for lengths between 100 and 800 AA exactly as in the manuscript. For larger proteins CUDA_UNIFIED_MEMORY is needed. This can be done by localy running the code on a CUDA capeable GPU with sufficient memory (A100 80GB e.g.) and running the code with the environment variables XLA_PYTHON_CLIENT_MEM_FRACTION=100.0 TF_FORCE_UNIFIED_MEMORY=1
  274. def rg_loss(inputs, outputs):
  275. positions = outputs["structure_module"]["final_atom_positions"]
  276. ca = positions[::5,residue_constants.atom_order["CA"]]
  277. center = ca.mean(0)
  278. rg = jnp.sqrt(jnp.square(ca - center).sum(-1).mean() + 1e-8)
  279. rg_th = 2.38 * ca.shape[0] ** 0.365
  280. rg = jax.nn.elu(rg - rg_th)
  281. return {"rg":rg}
  282. for length in [100,200,300,400,500,600,700,800]:
  283. model = mk_afdesign_model(protocol="hallucination",loss_callback=rg_loss)
  284. model.prep_inputs(length=length)
  285. print("weights",model.opt["weights"])
  286. print('Starting up and compiling JAX model....')
  287. for i in range(10):
  288. model.restart(mode=["gumbel", "soft"],rm_aa="C")
  289. model.opt["weights"]["rg"] = 0.1
  290. if length > 600:
  291. model.opt["weights"]["rg"] = 0.01
  292. #model.opt["weights"]['helix'] = -0.1
  293. model.opt["weights"]['plddt'] = 1.0
  294. model.opt["weights"]['pae'] = 1.0
  295. model.opt["weights"]['helix'] = -0.1
  296. print("weights", model.opt["weights"])
  297. model.design_logits(100)
  298. #change the output path for local execution
  299. model.save_pdb(f"Hallo_{i}.pdb")
  300. # %%
  301. #@markdown #Redesign with ProteinMPNN for ESMFold prediction
  302. #@markdown The standard manuscript settings were 8 sequences, 0.1 sampling temperature and the removal of cysteines
  303. import pickle
  304. num_seqs = 8 #@param ["8", "16", "32", "64"] {type:"raw"}
  305. mpnn_sampling_temp = 0.1 #@param ["0.0001", "0.1", "0.15", "0.2", "0.25", "0.3", "0.5", "1.0"] {type:"raw"}
  306. rm_aa = "C" #@param {type:"string"}
  307. use_solubleMPNN = False #@param {type:"boolean"}
  308. #@markdown - `mpnn_sampling_temp` - control diversity of sampled sequences. (higher = more diverse).
  309. #@markdown - `rm_aa='C'` - do not use [C]ysteines.
  310. #@markdown - `use_solubleMPNN` - use weights trained only on soluble proteins.
  311. #@markdown
  312. from colabdesign.shared.protein import alphabet_list as chain_list
  313. mpnn_model = mk_mpnn_model()
  314. mpnn_model.prep_inputs(pdb_filename=f"{af_model.protocol}.pdb",
  315. chain=",".join(chain_list[:COPIES]),
  316. homooligmer=COPIES>1,
  317. rm_aa=rm_aa,
  318. weights = "soluble" if use_solubleMPNN else"original")
  319. out = mpnn_model.sample(num=num_seqs//8,
  320. batch=8,
  321. temperature=mpnn_sampling_temp)
  322. for seq,score in zip(out["seq"],out["score"]):
  323. print(score,seq.split("/")[0])
  324. df = pd.DataFrame(out["seq"])
  325. # Define the output path for saving the sequences as a .pkl file
  326. output_pkl_file = "redesigned_sequences.pkl"
  327. # Save the DataFrame to a .pkl file
  328. with open(output_pkl_file, 'wb') as f:
  329. pickle.dump(df, f)
  330. # %%
  331. #@markdown #Run ESMFold to test designability
  332. #@markdown This cells runs ESMFold from huggingface and automatically calculates the RMSD to the designed backbone
  333. #@markdown NOTE: GPU memory can be a big problem here. If you get memory errors please restart the runtime and run this cell again. It should be self contained. Additionally, after finish the ESMFold prediction rerun the setup cell
  334. import os
  335. import pandas as pd
  336. from Bio.PDB import PDBParser, Superimposer
  337. import pickle
  338. import torch
  339. import numpy as np
  340. from transformers import AutoTokenizer, EsmForProteinFolding
  341. from transformers.models.esm.openfold_utils.protein import to_pdb, Protein as OFProtein
  342. from transformers.models.esm.openfold_utils.feats import atom14_to_atom37
  343. output_pkl_file = "redesigned_sequences.pkl"
  344. with open(output_pkl_file, 'rb') as f:
  345. seq = pickle.load(f)
  346. seq_list = []
  347. for i in np.asarray(seq):
  348. seq_list.append(i[0])
  349. pdb_file = "hallucination.pdb"
  350. print(seq_list)
  351. tokenizer = AutoTokenizer.from_pretrained("facebook/esmfold_v1")
  352. model = EsmForProteinFolding.from_pretrained("facebook/esmfold_v1", low_cpu_mem_usage=True)
  353. device = 'cuda:0'
  354. model = model.cuda(device)
  355. model.esm = model.esm.half()
  356. model.trunk.set_chunk_size(64)
  357. torch.backends.cuda.matmul.allow_tf32 = True
  358. def convert_outputs_to_pdb(outputs):
  359. final_atom_positions = atom14_to_atom37(outputs["positions"][-1], outputs)
  360. outputs = {k: v.to("cpu").numpy() for k, v in outputs.items()}
  361. final_atom_positions = final_atom_positions.cpu().numpy()
  362. final_atom_mask = outputs["atom37_atom_exists"]
  363. pdbs = []
  364. for i in range(outputs["aatype"].shape[0]):
  365. aa = outputs["aatype"][i]
  366. pred_pos = final_atom_positions[i]
  367. mask = final_atom_mask[i]
  368. resid = outputs["residue_index"][i] + 1
  369. pred = OFProtein(
  370. aatype=aa,
  371. atom_positions=pred_pos,
  372. atom_mask=mask,
  373. residue_index=resid,
  374. b_factors=outputs["plddt"][i],
  375. chain_index=outputs["chain_index"][i] if "chain_index" in outputs else None,
  376. )
  377. pdbs.append(to_pdb(pred))
  378. return pdbs
  379. def calculate_ca_rmsd(pdb_file1, pdb_file2):
  380. parser = PDBParser(QUIET=True)
  381. structure1 = parser.get_structure("Protein1", pdb_file1)
  382. structure2 = parser.get_structure("Protein2", pdb_file2)
  383. ca_atoms1 = [atom for atom in structure1.get_atoms() if atom.get_name() == "CA"]
  384. ca_atoms2 = [atom for atom in structure2.get_atoms() if atom.get_name() == "CA"]
  385. super_imposer = Superimposer()
  386. super_imposer.set_atoms(ca_atoms1, ca_atoms2)
  387. super_imposer.apply(structure2.get_atoms())
  388. rmsd = super_imposer.rms
  389. return rmsd
  390. def process_sequences(seq_list, pdb_file):
  391. lowest_rmsd = float('inf')
  392. lowest_rmsd_data = None
  393. out_ss_path = "./output"
  394. if not os.path.exists(out_ss_path):
  395. os.mkdir(out_ss_path)
  396. for test_protein in seq_list:
  397. data = {}
  398. tokenized_input = tokenizer([test_protein], return_tensors="pt", add_special_tokens=False)['input_ids']
  399. tokenized_input = tokenized_input.cuda(device)
  400. with torch.no_grad():
  401. output = model(tokenized_input)
  402. data['out'] = output
  403. data["plddt"] = torch.mean(output['plddt']).item()
  404. data['pae'] = torch.mean(output['predicted_aligned_error']).item()
  405. pdb_data = convert_outputs_to_pdb(output)
  406. tmp_pdb_file = os.path.join(out_ss_path, "TMP.pdb")
  407. with open(tmp_pdb_file, 'w') as file:
  408. for line in pdb_data:
  409. file.write(line)
  410. data['rmsd'] = calculate_ca_rmsd(tmp_pdb_file, pdb_file)
  411. print(f'Sequence: {test_protein}, plddt: {data["plddt"]}, PAE: {data["pae"]}, RMSD: {data["rmsd"]}')
  412. if data['rmsd'] < lowest_rmsd:
  413. lowest_rmsd = data['rmsd']
  414. lowest_rmsd_data = data
  415. if lowest_rmsd_data is not None:
  416. print(f'Lowest RMSD: {lowest_rmsd}')
  417. best_pdb_data = convert_outputs_to_pdb(lowest_rmsd_data['out'])
  418. best_pdb_file = os.path.join(out_ss_path, "best_structure.pdb")
  419. with open(best_pdb_file, 'w') as file:
  420. for line in best_pdb_data:
  421. file.write(line)
  422. original_dict = lowest_rmsd_data
  423. key_to_exclude = 'out'
  424. data_out = {k: v for k, v in original_dict.items() if k != key_to_exclude}
  425. with open(os.path.join(out_ss_path, "best_structure_data.pkl"), 'wb') as f:
  426. pickle.dump(data_out, f)
  427. return lowest_rmsd, best_pdb_file, data_out
  428. return None, None, None
  429. lowest_rmsd, best_pdb_file, best_data = process_sequences(seq_list, pdb_file)
  430. if lowest_rmsd is not None:
  431. print(f"Lowest RMSD: {lowest_rmsd}, Best PDB file: {best_pdb_file}")
  432. else:
  433. print("No valid result found.")
  434. # %%
  435. #@title # Heterodimer Design Prep
  436. #@markdown Design a set of heterodimeric proteins with two chains making a complex. The settings are excatly the ones used in the manuscript to design the heterodimer binders.
  437. LENGTH1 = 100 #@param {type:"integer"}
  438. LENGTH2 = 100 #@param {type:"integer"}
  439. #@markdown ProteinMPNN Settings
  440. use_solubleMPNN = True #@param {type:"boolean"}
  441. #@markdown
  442. from colabdesign.af.alphafold.common import residue_constants
  443. import jax
  444. import jax.numpy as jnp
  445. def hd_loss(inputs, outputs):
  446. positions = outputs["structure_module"]["final_atom_positions"]
  447. ca1 = positions[:LENGTH1, residue_constants.atom_order["CA"]]
  448. center1 = ca1.mean(0)
  449. rg1 = jnp.sqrt(jnp.square(ca1 - center1).sum(-1).mean() + 1e-8)
  450. rg_th = 2.38 * ca1.shape[0] ** 0.365
  451. rg1 = jax.nn.elu(rg1 - rg_th)
  452. ca2 = positions[LENGTH2:, residue_constants.atom_order["CA"]]
  453. center2 = ca2.mean(0)
  454. rg2 = jnp.sqrt(jnp.square(ca2 - center2).sum(-1).mean() + 1e-8)
  455. rg_th = 2.38 * ca2.shape[0] ** 0.365
  456. rg2 = jax.nn.elu(rg2 - rg_th)
  457. return {"hd":rg1+rg2}
  458. total_length = LENGTH1 + LENGTH2
  459. clear_mem()
  460. af_model = mk_afdesign_model(protocol="hallucination", loss_callback=hd_loss)
  461. af_model.prep_inputs(length=total_length)
  462. af_model._inputs['residue_index'][LENGTH1:] = np.arange(LENGTH2) + 50 + LENGTH1
  463. # add extra losses
  464. af_model.restart(mode=["gumbel", "soft"])
  465. af_model.opt["weights"]["hd"] = 0.1
  466. af_model.opt["weights"]['plddt'] = 1.0
  467. af_model.opt["weights"]['pae'] = 1.0
  468. af_model.opt["weights"]['helix'] = -0.5
  469. print("weights", af_model.opt["weights"])
  470. print('Starting up and compiling JAX model....')
  471. # %%
  472. #@title # Run Design
  473. af_model.design_logits(100)
  474. af_model.save_pdb("Heterodimer.pdb")
  475. # %%
  476. af_model.save_pdb("Heterodimer.pdb")
  477. af_model.plot_pdb()
  478. # %%
  479. #@title # Design Sequence using Homooligomer Filter
  480. #@markdown We first test if the two protomers are predictd to fold into a high confidence protein on their own, removing proteins that are not likely to be expressed on their own. Then we predict the heterodimer using the AF multimer model. Generally the AF multimer model has a hard time predicting de novo designed proteins. This is why we use templates and remove any interchain information. Finally we predict each individual protomer with a copy of itself, testing for homooligomerisation.
  481. file_path ="Heterodimer.pdb"
  482. folder_path = "/content/"
  483. ######## make A - B chain file
  484. from Bio.PDB import PDBParser, PDBIO, Chain
  485. # Set the input and output PDB file names
  486. input_pdb_file = file_path
  487. if not os.path.exists(os.path.join(folder_path, 'AB')):
  488. os.mkdir(os.path.join(folder_path, 'AB'))
  489. output_pdb_file = os.path.join(folder_path, 'AB',"Heterodimer.pdb")
  490. # Create a PDB parser and read the input PDB file
  491. parser = PDBParser()
  492. structure = parser.get_structure("input_structure", input_pdb_file)
  493. # Find the initial chain id
  494. initial_chain_id = None
  495. for chain in structure[0]:
  496. initial_chain_id = chain.get_id()
  497. break
  498. # Create new chains A and B
  499. chain_A = Chain.Chain("A")
  500. chain_B = Chain.Chain("B")
  501. # Iterate over the residues in the original chain
  502. for residue in structure[0][initial_chain_id]:
  503. res_id = residue.get_id()[1]
  504. # Add residues 1-200 to chain A
  505. if 1 <= res_id <= 100:
  506. chain_A.add(residue.copy())
  507. # Add residues 201-400 to chain B
  508. elif 151 <= res_id <= 450:
  509. chain_B.add(residue.copy())
  510. # Remove the existing chain
  511. for model in structure:
  512. model.detach_child(initial_chain_id)
  513. # Add the new chains to the model
  514. structure[0].add(chain_A)
  515. structure[0].add(chain_B)
  516. # Save the modified structure to a new PDB file
  517. io = PDBIO()
  518. io.set_structure(structure)
  519. io.save(output_pdb_file)
  520. clear_mem()
  521. he_model = mk_afdesign_model(protocol="fixbb", use_templates=True, use_multimer=True)
  522. ho_model = mk_afdesign_model(protocol="hallucination")
  523. ho_model.prep_inputs(length=LENGTH1, copies=2)
  524. ho_model.set_weights(i_pae=1.0)
  525. s_model = mk_afdesign_model(protocol="hallucination")
  526. s_model.prep_inputs(length=LENGTH2)
  527. mpnn_model = mk_mpnn_model(weights="soluble")
  528. mpnn_model.prep_inputs(pdb_filename=output_pdb_file, chain='A,B',rm_aa="C")
  529. samples = mpnn_model.sample_parallel(8)
  530. he_model.prep_inputs(pdb_filename=output_pdb_file, chain='A,B',rm_template_ic=True)
  531. he_model._inputs['residue_index'][LENGTH1:] = np.arange(LENGTH2) + 50 + LENGTH1
  532. k = 0
  533. for seq in samples['seq']:
  534. print('Predicting Protomer 1...')
  535. s_model.predict(seq=seq[:LENGTH1], num_recycles=3)
  536. plddt1 = s_model.aux['losses']['plddt']
  537. print('Predicting Protomer 2...')
  538. s_model.predict(seq=seq[LENGTH1+1:], num_recycles=3)
  539. plddt2 = s_model.aux['losses']['plddt']
  540. k = k + 1
  541. if plddt1 < 0.20 and plddt2 < 0.20:
  542. print('Passed Protomer Check! Predicting Heterodimer...')
  543. he_model.predict(seq=''.join([seq[:LENGTH1], seq[LENGTH1+1:]]), num_recycles=3)
  544. if he_model.aux['losses']['plddt'] < 0.15 and he_model.aux['losses']['rmsd'] < 2.0:
  545. print('Passed Heterodimer Check! Predicting Homodimer 1...')
  546. ho_model.predict(seq=seq[:LENGTH1],num_recycles=3)
  547. print('Predicting Homodimer 2...')
  548. ipae1 = ho_model.aux['losses']['i_pae']
  549. ho_model.predict(seq=seq[LENGTH1+1:],num_recycles=3)
  550. ipae2 = ho_model.aux['losses']['i_pae']
  551. if ipae1 > 0.8 and ipae2 > 0.8:
  552. print('Passed Homodimer check!')
  553. he_model.save_pdb(f'Heterodimer_seq_{k}.pdb')
  554. # %%
  555. def get_pdb(pdb_code=""):
  556. if pdb_code is None or pdb_code == "":
  557. upload_dict = files.upload()
  558. pdb_string = upload_dict[list(upload_dict.keys())[0]]
  559. with open("tmp.pdb","wb") as out: out.write(pdb_string)
  560. return "tmp.pdb"
  561. elif os.path.isfile(pdb_code):
  562. return pdb_code
  563. elif len(pdb_code) == 4:
  564. os.system(f"wget -qnc https://files.rcsb.org/view/{pdb_code}.pdb")
  565. return f"{pdb_code}.pdb"
  566. else:
  567. os.system(f"wget -qnc https://alphafold.ebi.ac.uk/files/AF-{pdb_code}-F1-model_v3.pdb")
  568. return f"AF-{pdb_code}-F1-model_v3.pdb"
  569. def add_rg_loss(self, weight=0.1):
  570. '''add radius of gyration loss'''
  571. def loss_fn(inputs, outputs):
  572. xyz = outputs["structure_module"]
  573. ca = xyz["final_atom_positions"][:,residue_constants.atom_order["CA"]]
  574. ca = ca[-self._binder_len:]
  575. rg = jnp.sqrt(jnp.square(ca - ca.mean(0)).sum(-1).mean() + 1e-8)
  576. rg_th = 2.38 * ca.shape[0] ** 0.365
  577. rg = jax.nn.elu(rg - rg_th)
  578. return {"rg":rg}
  579. self._callbacks["model"]["loss"].append(loss_fn)
  580. self.opt["weights"]["rg"] = weight
  581. #@title # Binder Design
  582. #@markdown For a given length, generate/hallucinate a protein sequence that AlphaFold thinks folds into a well structured protein (high plddt, low pae, many contacts).
  583. LENGTH = 100 #@param {type:"integer"}
  584. binder_pdb = '5NGV' #@param {type:"string"}
  585. binder_chain ='A' #@param {type:"string"}
  586. hotspot ='' #@param {type:"string"}
  587. if hotspot == "": hotspot = None
  588. #@markdown ProteinMPNN Settings
  589. use_solubleMPNN = True #@param {type:"boolean"}
  590. #@markdown
  591. clear_mem()
  592. af_model = mk_afdesign_model(protocol="binder")
  593. add_rg_loss(af_model)
  594. af_model.prep_inputs(pdb_filename=get_pdb(binder_pdb), chain=binder_chain,hotspot=hotspot, binder_len=LENGTH)
  595. af_model.restart(mode=["gumbel", "soft"])
  596. af_model.opt["weights"]["rg"] = 0.5
  597. af_model.opt["weights"]['helix'] = -0.2
  598. af_model.opt["weights"]['plddt'] = 0.1
  599. af_model.opt["weights"]['pae'] = 0.1
  600. af_model.opt["weights"]['i_pae'] = 0.1
  601. af_model.opt["weights"]['i_con'] = 2.0
  602. print("weights", af_model.opt["weights"])
  603. print('Starting up and compiling JAX model....')
  604. # %%
  605. af_model.design_logits(100)
  606. af_model.save_pdb("Binder.pdb")
  607. # %%
  608. af_model.plot_pdb()
  609. # %%
  610. #@title # Binder Sequence Design with AF Multimer filtering
  611. #@markdown Use this to generate sequences for the binder candidate generated in the previous step
  612. #@markdown First we use the AF2 PTM model to predict the binder without receptor, acting as a fast pre filter. Then we use the AF Multimer model to predict the Receptor Binder complex. Again we use a template for the binder to help AF Multimer predicting the de novo designed protein
  613. binder_model = mk_afdesign_model(protocol="binder",use_multimer=True,use_initial_guess=True)
  614. hall_model = mk_afdesign_model(protocol="fixbb")
  615. binder_model.set_weights(i_pae=1.0)
  616. mpnn_model = mk_mpnn_model(weights="soluble")
  617. mpnn_model.prep_inputs(pdb_filename="Binder.pdb", chain='A,B', fix_pos='A',rm_aa="C")
  618. samples = mpnn_model.sample_parallel(8,temperature=0.01)
  619. hall_model.prep_inputs(pdb_filename="Binder.pdb", chain='B')
  620. binder_model.prep_inputs(pdb_filename="Binder.pdb", chain='A', binder_chain='B',use_binder_template=True,rm_template_ic=True)
  621. k=0
  622. for seq in samples['seq']:
  623. print("Predicting binder only")
  624. hall_model.predict(seq=seq[-LENGTH:], num_recycles=3)
  625. if hall_model.aux['losses']['rmsd'] < 2.0 :
  626. print("Passed! Predicting binder with receptor using AF Multimer")
  627. binder_model.predict(seq=seq[-LENGTH:], num_recycles=3)
  628. plddt1 = binder_model.aux['losses']['plddt']
  629. i_pae = binder_model.aux['losses']['i_pae']
  630. if plddt1 < 0.15 and i_pae < 0.4:
  631. print(f"Passed! Final I_PAE is {i_pae*31}")
  632. binder_model.save_pdb(f'Binder_seq_{k}.pdb')
  633. binder_model.plot_pdb()
  634. k = k + 1
  635. # %%
  636. #@title # Site scaffolding example
  637. #@markdown This cell provides the code to perform the site scaffolding in bulk.
  638. #@markdown Just go to the commented section with names, contigs and length to insert the desired PDB identifier, contigs and final size and start designing.
  639. #@markdown Num_designs controls how many backbones one designes per PDB file
  640. num_designs = 1 #@param {type:"integer"}
  641. def get_pdb(pdb_code=""):
  642. if pdb_code is None or pdb_code == "":
  643. upload_dict = files.upload()
  644. pdb_string = upload_dict[list(upload_dict.keys())[0]]
  645. with open("tmp.pdb","wb") as out: out.write(pdb_string)
  646. return "tmp.pdb"
  647. elif os.path.isfile(pdb_code):
  648. return pdb_code
  649. elif len(pdb_code) == 4:
  650. os.system(f"wget -qnc https://files.rcsb.org/view/{pdb_code}.pdb")
  651. return f"{pdb_code}.pdb"
  652. else:
  653. os.system(f"wget -qnc https://alphafold.ebi.ac.uk/files/AF-{pdb_code}-F1-model_v3.pdb")
  654. return f"AF-{pdb_code}-F1-model_v3.pdb"
  655. from colabdesign import mk_afdesign_model, clear_mem
  656. import contextlib
  657. from colabdesign.af.alphafold.common import residue_constants
  658. import jax
  659. import jax.numpy as jnp
  660. import pickle
  661. from colabdesign.mpnn import mk_mpnn_model
  662. import re
  663. import os
  664. #Add the names of the PDB files for the scaffolding problem here
  665. names = [
  666. "1PRW"
  667. ]
  668. print(len(names))
  669. #Add the design contigs here
  670. inputs = [
  671. "5-20,A16-35,10-25,A52-71,5-20"
  672. ]
  673. #Add the total length here. We only use the maximum length specified
  674. lengths = [
  675. "60-105"
  676. ]
  677. def rg_loss(inputs, outputs):
  678. positions = outputs["structure_module"]["final_atom_positions"]
  679. ca = positions[::5, residue_constants.atom_order["CA"]]
  680. center = ca.mean(0)
  681. rg = jnp.sqrt(jnp.square(ca - center).sum(-1).mean() + 1e-8)
  682. rg_th = 2.38 * ca.shape[0] ** 0.365
  683. rg = jax.nn.elu(rg - rg_th)
  684. return {"rg": rg}
  685. clear_mem()
  686. for _name, _input, _length in zip(
  687. names, inputs, lengths
  688. ):
  689. print(f"Starting on {_name}")
  690. _input = _input.replace(" ", "")
  691. __name = _name.split("_")[0]
  692. model = mk_afdesign_model(
  693. protocol="partial"
  694. )
  695. wire_loop_repr = ["l" if re.search("[A-Z]", x) else "w" for x in _input.split(",")]
  696. _lengths = []
  697. for _id, rep in zip(wire_loop_repr, _input.split(",")):
  698. if "-" in rep: # loop or range
  699. if _id == "l": # loop
  700. rep = rep[1:]
  701. _len = int(rep.split("-")[1]) - int(rep.split("-")[0]) + 1
  702. else: # range
  703. _len = int(rep.split("-")[1])
  704. else:
  705. if _id == "l":
  706. rep = rep[1:]
  707. _len = int(1)
  708. _lengths.append(_len)
  709. overall_length = sum(_lengths)
  710. print(overall_length)
  711. old_pos = list(filter(lambda x: re.search("[A-Z]", x), _input.split(",")))
  712. order = list(range(len(old_pos)))
  713. old_pos = ",".join(old_pos)
  714. wires = list(filter(lambda x: not re.search("[A-Z]", x), _input.split(",")))
  715. wires = [
  716. int(wire) if "-" not in wire else int(wire.split("-")[1]) for wire in wires
  717. ]
  718. offset = wires[0] if not wire_loop_repr[0] == "l" else 0
  719. if wire_loop_repr[0] == "w":
  720. wires = wires[1:]
  721. if wire_loop_repr[-1] == "w":
  722. wires = wires[:-1]
  723. chain = re.findall("[A-Z]", _input)
  724. chain = list(set(chain))
  725. assert len(chain) == 1
  726. chain = chain[0]
  727. if "-" in _length:
  728. _length = _length.split("-")[1]
  729. _length = int(_length)
  730. if _length < overall_length:
  731. _length = overall_length
  732. debug = False
  733. if debug:
  734. print("chain " + str(chain))
  735. print("old_pos " + str(old_pos))
  736. print("wires " + str(wires))
  737. print("offset " + str(offset))
  738. print("_length " + str(_length))
  739. print("order " + str(order))
  740. print(_name)
  741. pdb_file = get_pdb(_name)
  742. model.prep_inputs(
  743. pdb_file,
  744. chain=chain,
  745. pos=old_pos,
  746. length=_length,
  747. fix_seq=True,
  748. )
  749. model.rewire(
  750. order=order, # set order of segments
  751. loops=wires, # change loop length inbetween segments
  752. offset=offset,
  753. ) # essentially loop length at the N term
  754. print(" Starting up and compiling JAX model....")
  755. for i in range(num_designs):
  756. print(f" Iteration {i} of 100")
  757. model.restart(mode=["gumbel", "soft"], rm_aa="C")
  758. model.opt["weights"]["rg"] = 0.1
  759. model.opt["weights"]["dgram_cce"] = 2.0
  760. model.opt["weights"]["plddt"] = 0.1
  761. model.opt["weights"]["pae"] = 0.1
  762. model.opt["weights"]["rmsd"] = 1.0
  763. model.opt["weights"]['sc_rmsd'] = 1.0
  764. # model.opt["weights"]['fape'] = 1.0
  765. model.design_logits(190)
  766. model.design_logits(10, save_best=True)
  767. outfile = f"out_sc/{_name}_resesigned/{_name}_redesigned_{i}.pdb"
  768. os.makedirs(os.path.dirname(outfile), exist_ok=True)
  769. model.save_pdb(outfile)
  770. mpnn_model = mk_mpnn_model()
  771. p = (
  772. []
  773. ) # [homo if not n in _interfaceFixturesIndexSecChain else hetero for n, (homo, hetero) in enumerate(zip(list(ho2), list(he[-len(ho2):])))]
  774. for k in model.opt["pos"]:
  775. p.append(str(k + 1)) # Might be wrong
  776. p.append(",")
  777. posf = "".join(p[:-1])
  778. repredictionModel = mk_afdesign_model(
  779. protocol="fixbb", use_templates=False
  780. )
  781. os.makedirs(os.path.dirname('out_sc_Redesigned/'), exist_ok=True)
  782. for j in range(num_designs):
  783. print(f" Reprediction Iteration {j} of 100")
  784. repredictionModel.prep_inputs(
  785. f"out_sc/{_name}_resesigned/{_name}_redesigned_{j}.pdb"
  786. )
  787. mpnn_model.prep_inputs(
  788. pdb_filename=f"out_sc/{_name}_resesigned/{_name}_redesigned_{j}.pdb",
  789. chain="A",
  790. fix_pos=posf,
  791. rm_aa="C",
  792. )
  793. out = mpnn_model.sample(num=1, batch=8, temperature=0.1)
  794. for n, i in enumerate(out["seq"]):
  795. repredictionModel.predict(seq=i, num_recycles=3)
  796. if (
  797. repredictionModel.aux["log"]["rmsd"] < 2.0
  798. and repredictionModel.aux["log"]["plddt"] > 0.85
  799. ):
  800. filename = f'out_sc_Redesigned/{_name}_resesigned/{_name}_redesigned-{j}_num-{n}_rmsd-{int(repredictionModel.aux["log"]["rmsd"]*100)}.pdb'
  801. os.makedirs(os.path.dirname(filename), exist_ok=True)
  802. repredictionModel.save_pdb(filename)
  803. for _name, _input, _length in zip(
  804. names, inputs, lengths
  805. ):
  806. print(f"Starting on {_name}")
  807. clear_mem()
  808. _input = _input.replace(" ", "")
  809. __name = _name.split("_")[0]
  810. test_model = mk_afdesign_model(protocol='fixbb')
  811. model = mk_afdesign_model(
  812. protocol="partial", use_templates=False
  813. ) # set True to constrain positions using template input
  814. # define positions we want to constrain (input PDB numbering)
  815. wire_loop_repr = ["l" if re.search("[A-Z]", x) else "w" for x in _input.split(",")]
  816. _lengths = []
  817. for _id, rep in zip(wire_loop_repr, _input.split(",")):
  818. if "-" in rep: # loop or range
  819. if _id == "l": # loop
  820. rep = rep[1:]
  821. _len = int(rep.split("-")[1]) - int(rep.split("-")[0]) + 1
  822. else: # range
  823. _len = int(rep.split("-")[1])
  824. else:
  825. if _id == "l":
  826. rep = rep[1:]
  827. _len = 1
  828. _lengths.append(_len)
  829. overall_length = sum(_lengths)
  830. old_pos = list(filter(lambda x: re.search("[A-Z]", x), _input.split(",")))
  831. order = list(range(len(old_pos)))
  832. old_pos = ",".join(old_pos)
  833. wires = list(filter(lambda x: not re.search("[A-Z]", x), _input.split(",")))
  834. wires = [
  835. int(wire) if "-" not in wire else int(wire.split("-")[1]) for wire in wires
  836. ]
  837. offset = wires[0] if not wire_loop_repr[0] == "l" else 0
  838. if wire_loop_repr[0] == "w":
  839. wires = wires[1:]
  840. if wire_loop_repr[-1] == "w":
  841. wires = wires[:-1]
  842. chain = re.findall("[A-Z]", _input)
  843. chain = list(set(chain))
  844. assert len(chain) == 1
  845. chain = chain[0]
  846. if "-" in _length:
  847. _length = _length.split("-")[1]
  848. _length = int(_length)
  849. if _length < overall_length:
  850. _length = overall_length
  851. print(_name)
  852. pdb_file = get_pdb(_name)
  853. model.prep_inputs(
  854. pdb_file,
  855. chain=chain,
  856. pos=old_pos, # define positions to contrain
  857. length=_length, # define if the desired length is different from input PDB
  858. fix_seq=True,
  859. ) # set True to constrain the sequence
  860. # set positions (if different from PDB)
  861. # reorder the segments,
  862. model.rewire(
  863. order=order, # set order of segments
  864. loops=wires, # change loop length inbetween segments
  865. offset=offset,
  866. ) # essentially loop length at the N term
  867. in_files = os.listdir(f'out_sc_Redesigned/{_name}_resesigned/')
  868. if not os.path.exists(f'out_sc_Redesigned/{_name}_resesigned/out/'):
  869. os.mkdir(f'out_sc_Redesigned/{_name}_resesigned/out/')
  870. for ii in in_files:
  871. if ii[-1] == 'b':
  872. test_model.prep_inputs(pdb_filename=f'out_sc_Redesigned/{_name}_resesigned/{ii}')
  873. seq = test_model._inputs['batch']["aatype"]
  874. #print(seq)
  875. model.predict(seq=seq, num_recycles=3)
  876. if model.aux["losses"]["rmsd"] < 1.0:
  877. model.save_pdb(f'out_sc_Redesigned/{_name}_resesigned/out/{ii}')
  878. with open(f'out_sc_Redesigned/{_name}_resesigned/out/{ii[:-4]}_data.pkl', 'wb') as f:
  879. pickle.dump(model.aux["losses"]["rmsd"], f)
  880. # %%

RSO.ipynb at commit e31a56f, under other · at the source

Overview

Authors: Mak B Djulbegovic1, Nedym Hadzijahic2, David J Taylor Gonzalez3, Michael Antonietti4, Sidra Zafar1,5, Ajay E Kuriyan1,2
  1. Wills Eye Hospital, Thomas Jefferson University Hospital, Philadelphia, Pennsylvania
  2. University of Miami, Miami, Florida
  3. Department of Ophthalmology, Broward Health North, Pompano Beach, Florida
  4. Department of Ophthalmology, Massachusetts Eye and Ear Infirmary, Harvard Medical School, Boston, Massachusetts
  5. Mid Atlantic Retina at Wills Eye Hospital, Philadelphia, Pennsylvania
Institutions: Thomas Jefferson University Hospital (United States); Wills Eye Hospital (United States); University of Miami (United States); Broward Health (United States); Massachusetts Eye and Ear Infirmary (United States); Harvard University (United States); Mid Atlantic Retina (United States)
Journal: Ophthalmology science, volume 6, issue 8, article 101249
Dates: received 2 February 2026; accepted 18 May 2026; published online 25 May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.xops.2026.101249 · PMID 42421755 · PMCID PMC13343151 · OpenAlex W7162299899
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism)
Keywords: Proliferative vitreoretinopathy, Epithelial–mesenchymal transition, Intrinsic disorder, SNAIL1, Artificial intelligence
Topic: Retinal Imaging and Analysis (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: VitreoRetinal Surgery Foundation
Citations: not cited yet (Europe PMC); 62 references in the paper

Abstract

Objective: Proliferative vitreoretinopathy (PVR) remains a major cause of failure after rhegmatogenous retinal detachment repair and lacks effective pharmacologic therapies. Although epithelial–mesenchymal transition (EMT) is central to PVR pathogenesis, the structural determinants governing the tractability of EMT regulators, particularly those involving intrinsic disorder, remain poorly defined. We developed a disorder-aware, artificial intelligence–enabled computational framework to evaluate EMT-associated proteins in PVR and prioritize structurally tractable regulators for structure-based targeting.

Design: A computational, hypothesis-generating study employing an in silico screening and structural modeling pipeline.

Subjects: No human subjects or biological specimens were included. The dataset comprised 25 EMT-associated proteins implicated in PVR, curated through a narrative review of peer-reviewed literature.

Methods: Candidate proteins were evaluated using a multistage pipeline integrating intrinsic disorder profiling (Rapid Intrinsic Disorder Analysis Online), redox-sensitive disorder-to-order transition (DOT) analysis (AIUPred), and protein–protein interaction network assessment (Search Tool for the Retrieval of Interacting Genes/Proteins [STRING]). Structure-based modeling and generative binder design were then applied to the top-ranked candidate using RFdiffusion for de novo backbone generation, protein message passing neural network for sequence design, and AlphaFold2 for structural validation.

Main Outcome Measures: Primary measures were the proportion of intrinsically disordered residues, redox-sensitive disorder change, STRING network coherence within EMT-related pathways, and the structural consistency of the designed binder–target complex, assessed by root mean square deviation (RMSD) and mean per-residue confidence (predicted local distance difference test [pLDDT]).

Results: Of the 25 EMT-associated proteins screened, several exhibited intermediate intrinsic disorder profiles and measurable DOT potential. Snail Family Transcriptional Repressor 1 (SNAIL1) emerged as the highest-priority candidate, demonstrating an intermediate intrinsic disorder profile (∼35%), a pronounced redox-sensitive DOT region, and selective connectivity within EMT-related signaling networks. Functional mapping of the SNAIL1 C-terminal DOT segment identified 6 basic residues with literature-supported or motif-based regulatory significance (K187, R191, R224, K234, K253, and R264). Following sequence design and structural validation, the top-ranked binder exhibited the lowest structural deviation within the generated ensemble (RMSD 18.5 Å) and high per-residue confidence (mean pLDDT 0.84).

Conclusions: Our study introduces a disorder-informed computational framework for prioritizing structurally tractable EMT regulators in PVR. As a proof-of-concept, the pipeline nominates SNAIL1 and generates a structure-aware de novo binder targeting its C-terminal DOT region, providing a foundation for disorder-based therapeutic discovery in fibrotic retinal disease.

Financial Disclosure(s): Proprietary or commercial disclosure may be found in the Footnotes and Disclosures at the end of this article.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.

sokrypton/colabdesign

License: other
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: e31a56fe1d9b4de25c8697f3a28b75892941cc72, 23 October 2025
Languages: Python (95), Jupyter (28), JavaScript (1)
Size: 155 files, 124 scripts
Software Heritage: not archived
Found in: the text, “RFdiffusion-Based Binder Design”
Holds: README, license file, environment (setup.py), continuous integration, 28 notebooks
Not found: CITATION.cff, tests, documentation
Tools: NumPy (77 files), JAX (71 files), Matplotlib (14 files), SciPy (10 files), pandas (7 files), Biopython (5 files), PyTorch (5 files), Plotly (2 files), Hugging Face Transformers (2 files), Keras (1 file), TensorFlow (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
126 files

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 124 scripts, each with its path and the digest of its content;
  • 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Authors: added Nedym Hadzijahic (0009-0004-1454-2705); removed Nedym Hadzijahic

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 5 keywords, 1 funder, 62 references.

Cite

This paper

Djulbegovic, M. B., Hadzijahic, N., Taylor Gonzalez, D. J., Antonietti, M., Zafar, S., & Kuriyan, A. E. (2026). A Disorder-Aware Computational Framework to Identify Structurally Tractable Targets in Proliferative Vitreoretinopathy. Ophthalmology science, 6(8), 101249. https://doi.org/10.1016/j.xops.2026.101249

BibTeX

@article{djulbegovic2026disorder,
author = {Djulbegovic, Mak B and Hadzijahic, Nedym and Taylor Gonzalez, David J and Antonietti, Michael and Zafar, Sidra and Kuriyan, Ajay E},
title = {{A Disorder-Aware Computational Framework to Identify Structurally Tractable Targets in Proliferative Vitreoretinopathy}},
journal = {Ophthalmology science},
year = {2026},
month = may,
volume = {6},
number = {8},
pages = {101249},
publisher = {Elsevier},
issn = {2666-9145},
doi = {10.1016/j.xops.2026.101249},
url = {https://doi.org/10.1016/j.xops.2026.101249},
pmid = {42421755},
pmcid = {PMC13343151}
}

RIS

TY - JOUR
AU - Djulbegovic, Mak B
AU - Hadzijahic, Nedym
AU - Taylor Gonzalez, David J
AU - Antonietti, Michael
AU - Zafar, Sidra
AU - Kuriyan, Ajay E
TI - A Disorder-Aware Computational Framework to Identify Structurally Tractable Targets in Proliferative Vitreoretinopathy
T2 - Ophthalmology science
J2 - Ophthalmol Sci
PY - 2026
DA - 2026/05/25
VL - 6
IS - 8
SP - 101249
SN - 2666-9145
PB - Elsevier
DO - 10.1016/j.xops.2026.101249
UR - https://doi.org/10.1016/j.xops.2026.101249
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.xops.2026.101249",
"type": "article-journal",
"title": "A Disorder-Aware Computational Framework to Identify Structurally Tractable Targets in Proliferative Vitreoretinopathy",
"container-title": "Ophthalmology science",
"author": [
{
"family": "Djulbegovic",
"given": "Mak B"
},
{
"family": "Hadzijahic",
"given": "Nedym"
},
{
"family": "Taylor Gonzalez",
"given": "David J"
},
{
"family": "Antonietti",
"given": "Michael"
},
{
"family": "Zafar",
"given": "Sidra"
},
{
"family": "Kuriyan",
"given": "Ajay E"
}
],
"container-title-short": "Ophthalmol Sci",
"volume": "6",
"issue": "8",
"page": "101249",
"DOI": "10.1016/j.xops.2026.101249",
"PMID": "42421755",
"PMCID": "PMC13343151",
"ISSN": "2666-9145",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.xops.2026.101249",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
25
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: JAX, Biopython, Hugging Face Transformers, 8 other tools
[2] doi:10.1371/journal.pone.0346575 [code]
Statistically valid explainable black-box machine learning: applications in sex classification across species using brain imaging.
Journal: PloS one
In common: JAX, Hugging Face Transformers, TensorFlow, 6 other tools
[3] doi:10.1016/j.molcel.2026.07.006 [code]
DeorphaNN: Virtual screening of GPCR peptide agonists using AlphaFold-predicted active-state complexes and deep learning embeddings.
Journal: Molecular cell
In common: JAX, Biopython, TensorFlow, 5 other tools, 1 reference
[4] doi:10.1021/acs.biochem.5c00596 [code]
Cargo Recognition of Nesprin-2 by the Dynein Adapter Bicaudal D2 for a Nuclear Positioning Pathway That Is Important for Brain Development.
Journal: Biochemistry
In common: JAX, Biopython, TensorFlow, 5 other tools, 1 reference
[5] doi:10.1038/s41586-026-10658-6 [code]
An AI system to help scientists write expert-level empirical software.
Journal: Nature
In common: JAX, Hugging Face Transformers, TensorFlow, 5 other tools, 1 reference
[6] doi:10.1038/s41598-026-53415-5 [code]
Computational design and immunoinformatics validation of a T cell multi-epitope vaccine targeting glioblastoma stem cells.
Journal: Scientific reports
In common: JAX, Biopython, TensorFlow, 5 other tools
[7] doi:10.1038/s41586-026-10391-0 [code]
Cell-type-targeted mitochondrial transplantation rescues cell degeneration.
Journal: Nature
In common: JAX, Biopython, TensorFlow, 5 other tools
[8] doi:10.3390/ijms27156614 [code]
Candidalysin Inhibits &lt;i&gt;Porphyromonas gingivalis&lt;/i&gt; Lipoprotein-Induced IL-1β Production in BV-2 Microglia via Hydrophobic Microbial Interactions.
Journal: International journal of molecular sciences
In common: JAX, Biopython, TensorFlow, 5 other tools
[9] doi:10.1038/s41586-026-10670-w [code]
Zero-shot design of drug-binding proteins via neural iterative selection-expansion.
Journal: Nature
In common: Plotly, PyTorch, pandas, 3 other tools, 3 references
[10] doi:10.1038/s41592-026-03057-2 [code]
CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species.
Journal: Nature methods
In common: Biopython, Keras, TensorFlow, 5 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.