|
| 1 | +# Alchemical Protein Mutations |
| 2 | + |
| 3 | +In this tutorial you will learn how to use BioSimSpace's mapping functionality to set up alchemical calculations in order to compute the change in the binding affinity of a ligand as a result of a protein mutation. Specifically, we are going to focus on two proteins, first a set up of a single alchemical point mutation on ubiquitin, and second a set up on [aldose reductase](https://en.wikipedia.org/wiki/Aldose_reductase) (AR), which is a drug target for the treatment of diabetic nephropathy. It is recommended to complete [previous BioSimSpace tutorials](https://github.com/OpenBioSim/biosimspace_tutorials) before attempting this one. |
| 4 | + |
| 5 | +The relative change in the binding affinity as a result of a mutation, $\Delta \Delta G_{mut}$ can be calculated from the difference between free energy of mutation in the holo (bound) and apo (unbound) simulation legs, i.e.: |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +$$ |
| 10 | +\Delta \Delta G_{mut} = \Delta G_{holo} - \Delta G_{apo} |
| 11 | +$$ |
| 12 | + |
| 13 | +To get started, let's go through a simple example of generating the required input files in order to set up an alchemical mutation. |
| 14 | + |
| 15 | +## Simple Case - Input File Generation |
| 16 | + |
| 17 | +In order to create an alchemical protein system in BioSimSpace, we need two input protein structures, a wild-type and a mutant. We also need to make sure that the atom ordering between the two proteins is identical. Don't worry, this is an easy assumption to satisfy. We will load a structure `1UBQ` via [sire](https://sire.openbiosim.org/), which comes with bundled with BioSimSpace: |
| 18 | + |
| 19 | + |
| 20 | +```python |
| 21 | +import BioSimSpace as BSS |
| 22 | +import sire as sr |
| 23 | +mols = sr.load("1UBQ") |
| 24 | +``` |
| 25 | + |
| 26 | +There are multiple of ways of generating a mutant structure from a wild-type protein, some examples are: |
| 27 | +- [Pymol Mutagenesis Plugin](https://pymolwiki.org/index.php/Mutagenesis) (when exporting the mutant structure, you want to make you select 'retain atom ids' under 'PDB Options', or pass both input structures through *pdb4amber*) |
| 28 | +- [HTMD](https://software.acellera.com/htmd/tutorials/system-building-protein-protein.html#mutate-modified-residues) |
| 29 | +- [FoldX](https://foldxsuite.crg.eu/command/BuildModel) |
| 30 | +- [pdb4amber](https://ambermd.org/tutorials/basic/tutorial9/index.php) |
| 31 | + |
| 32 | +For this simple case we are going to use *pdb4amber* to mutate a threonine at position 9 to an alanine residue. First we are going to pass the wild-type protein from the crystal structure through *pdb4amber* in order create a consistent atom ordering between wild-type and mutant structures: |
| 33 | + |
| 34 | + |
| 35 | +```python |
| 36 | +!pdb4amber --reduce --dry --add-missing-atoms -o 1UBQ_dry_wt.pdb 1UBQ.pdb |
| 37 | +``` |
| 38 | + |
| 39 | +Next, we are going to create a mutant structure: |
| 40 | + |
| 41 | + |
| 42 | +```python |
| 43 | +!pdb4amber --reduce --dry -o 1UBQ_dry_t9a.pdb -m "9-ALA" --add-missing-atoms 1UBQ_dry_wt.pdb |
| 44 | +``` |
| 45 | + |
| 46 | +<div class="alert alert-block alert-warning"> |
| 47 | +<b>Warning:</b> This is a simple, but ultimately a crude way of generating a mutant structure. Different factors such as sidechain rotomers, packing and protonation states need to be taken into the account in order to accurately and robustly describe the mutant end-state. |
| 48 | +</div> |
| 49 | + |
| 50 | +## Simple Case - Alchemical System Generation |
| 51 | + |
| 52 | +Now that correct input files have been created, we can now proceed to create an alchemical protein in BioSimSpace. Let's load our two proteins: |
| 53 | + |
| 54 | + |
| 55 | +```python |
| 56 | +protein_wt = BSS.IO.readMolecules("1UBQ_dry_wt.pdb")[0] |
| 57 | +protein_mut = BSS.IO.readMolecules("1UBQ_dry_t9a.pdb")[0] |
| 58 | +``` |
| 59 | + |
| 60 | +Next, we want to parametrise them with our forcefield of choice: |
| 61 | + |
| 62 | + |
| 63 | +```python |
| 64 | +protein_wt = BSS.Parameters.ff14SB(protein_wt).getMolecule() |
| 65 | +protein_mut = BSS.Parameters.ff14SB(protein_mut).getMolecule() |
| 66 | +``` |
| 67 | + |
| 68 | +Now we want to compute the mapping between the two proteins, first let's figure out the residue index of our residue of interest (ROI): |
| 69 | + |
| 70 | + |
| 71 | +```python |
| 72 | +protein_wt.getResidues()[7:10] |
| 73 | +``` |
| 74 | + |
| 75 | + |
| 76 | +```python |
| 77 | +protein_mut.getResidues()[7:10] |
| 78 | +``` |
| 79 | + |
| 80 | +We can see that the residue with the index value of 8 are different between the two proteins. Let's pass this value to the [`BioSimSpace.Align.matchAtoms`](https://biosimspace.openbiosim.org/api/generated/BioSimSpace.Align.matchAtoms.html#BioSimSpace.Align.matchAtoms) function: |
| 81 | + |
| 82 | + |
| 83 | +```python |
| 84 | +mapping = BSS.Align.matchAtoms(molecule0=protein_wt, molecule1=protein_mut, roi=[8]) |
| 85 | +``` |
| 86 | + |
| 87 | +<div class="alert alert-block alert-info"> |
| 88 | +<b>Note:</b> You can also pass multiple residues of interest indices to the mapping if you wish to mutate several residues simultaneously. |
| 89 | +</div> |
| 90 | + |
| 91 | +Now that the mapping has been computed, we can visualise it: |
| 92 | + |
| 93 | + |
| 94 | +```python |
| 95 | +BSS.Align.viewMapping(protein_wt, protein_mut, mapping, roi=8, pixels=500) |
| 96 | +``` |
| 97 | + |
| 98 | +The computed atom mapping shows that both hydroxyl and methyl groups in the threonine side chain will be transformed into hydrogen atoms respectively. We can now proceed to align the two residues of interest: |
| 99 | + |
| 100 | + |
| 101 | +```python |
| 102 | +aligned_wt = BSS.Align.rmsdAlign(molecule0=protein_wt, molecule1=protein_mut, roi=[8]) |
| 103 | +``` |
| 104 | + |
| 105 | +Finally, we can create a merged alchemical protein system: |
| 106 | + |
| 107 | + |
| 108 | +```python |
| 109 | +merged_protein = BSS.Align.merge(aligned_wt, protein_mut, mapping, roi=[8]) |
| 110 | +``` |
| 111 | + |
| 112 | +The alchemical protein can now be solvated, ionised and exported to different file formats, for example GROMACS or [SOMD2, our OpenMM-based FEP engine](https://github.com/OpenBioSim/somd2): |
| 113 | + |
| 114 | + |
| 115 | +```python |
| 116 | +merged_system = merged_protein.toSystem() |
| 117 | + |
| 118 | +# solvate the system with the padding of 15 angstroms |
| 119 | +padding = 15 * BSS.Units.Length.angstrom |
| 120 | +box_min, box_max = merged_system.getAxisAlignedBoundingBox() |
| 121 | +box_size = [y - x for x, y in zip(box_min, box_max)] |
| 122 | +box_sizes = [x + padding for x in box_size] |
| 123 | + |
| 124 | +box, angles = BSS.Box.rhombicDodecahedronHexagon(max(box_sizes)) |
| 125 | +solvated_system = BSS.Solvent.tip3p(molecule=merged_system, box=box, angles=angles, ion_conc=0.15) |
| 126 | +``` |
| 127 | + |
| 128 | + |
| 129 | +```python |
| 130 | +# export the solvated system to GROMACS input files |
| 131 | +BSS.IO.saveMolecules("apo_ubiquitin_t9a", solvated_system, ["gro87", "grotop"]) |
| 132 | +``` |
| 133 | + |
| 134 | + |
| 135 | +```python |
| 136 | +# export the solvated system to SOMD2 input file |
| 137 | +BSS.Stream.save(solvated_system, "apo_ubiquitin_t9a") |
| 138 | +``` |
| 139 | + |
| 140 | +# Aldose Reductase - Alchemical System Generation |
| 141 | + |
| 142 | +## Apo System |
| 143 | + |
| 144 | +Now we are going to focus on the aldose reductase system and set up an alchemical transformation in both apo and holo forms of the protein. The input files (2PDG_8.0) were taken from the SI of a [paper by Aldeghi et. al](https://pubs.acs.org/doi/10.1021/acscentsci.8b00717), residue 47 mutated via PyMol (V47I), and standardised via *pdb4amber*. |
| 145 | + |
| 146 | + |
| 147 | +```python |
| 148 | +protein_wt = BSS.IO.readMolecules(BSS.IO.expand(BSS.tutorialUrl(), "aldose_reductase_dry.pdb"))[0] |
| 149 | +protein_mut = BSS.IO.readMolecules(BSS.IO.expand(BSS.tutorialUrl(), "aldose_reductase_v47i_dry.pdb"))[0] |
| 150 | +``` |
| 151 | + |
| 152 | +We can use `ensure_compatible=False` in order to get tLEaP to re-add the hydrogens for us: |
| 153 | + |
| 154 | + |
| 155 | +```python |
| 156 | +protein_wt = BSS.Parameters.ff14SB(protein_wt, ensure_compatible=False).getMolecule() |
| 157 | +protein_mut = BSS.Parameters.ff14SB(protein_mut, ensure_compatible=False).getMolecule() |
| 158 | +``` |
| 159 | + |
| 160 | + |
| 161 | +```python |
| 162 | +protein_wt.getResidues()[44:47] |
| 163 | +``` |
| 164 | + |
| 165 | + |
| 166 | +```python |
| 167 | +protein_mut.getResidues()[44:47] |
| 168 | +``` |
| 169 | + |
| 170 | +This time we are going to automatically detect the different residues between the two proteins: |
| 171 | + |
| 172 | + |
| 173 | +```python |
| 174 | +roi = [] |
| 175 | +for i, res in enumerate(protein_wt.getResidues()): |
| 176 | + if res.name() != protein_mut.getResidues()[i].name(): |
| 177 | + print(res, protein_mut.getResidues()[i]) |
| 178 | + roi.append(res.index()) |
| 179 | +``` |
| 180 | + |
| 181 | +We can then pass these residue indices to the mapping function as before: |
| 182 | + |
| 183 | + |
| 184 | +```python |
| 185 | +mapping = BSS.Align.matchAtoms(molecule0=protein_wt, molecule1=protein_mut, roi=roi) |
| 186 | +``` |
| 187 | + |
| 188 | + |
| 189 | +```python |
| 190 | +BSS.Align.viewMapping(protein_wt, protein_mut, mapping, roi=roi[0], pixels=500) |
| 191 | +``` |
| 192 | + |
| 193 | +The mapping shows that the perturbation will transform a hydrogen to a methyl group. Is this what we would expect for a valine to isoleucine transformation? If we are happy, we can proceed with the rest of the set up as before: |
| 194 | + |
| 195 | + |
| 196 | +```python |
| 197 | +aligned_wt = BSS.Align.rmsdAlign(molecule0=protein_wt, molecule1=protein_mut, roi=roi) |
| 198 | +merged_protein = BSS.Align.merge(aligned_wt, protein_mut, mapping, roi=roi) |
| 199 | +``` |
| 200 | + |
| 201 | + |
| 202 | +```python |
| 203 | +merged_system = merged_protein.toSystem() |
| 204 | +``` |
| 205 | + |
| 206 | + |
| 207 | +```python |
| 208 | +padding = 15 * BSS.Units.Length.angstrom |
| 209 | + |
| 210 | +box_min, box_max = merged_system.getAxisAlignedBoundingBox() |
| 211 | +box_size = [y - x for x, y in zip(box_min, box_max)] |
| 212 | +box_sizes = [x + padding for x in box_size] |
| 213 | +``` |
| 214 | + |
| 215 | + |
| 216 | +```python |
| 217 | +box, angles = BSS.Box.rhombicDodecahedronHexagon(max(box_sizes)) |
| 218 | +solvated_system = BSS.Solvent.tip3p(molecule=merged_system, box=box, angles=angles, ion_conc=0.15) |
| 219 | +``` |
| 220 | + |
| 221 | + |
| 222 | +```python |
| 223 | +# export the solvated system to GROMACS input files |
| 224 | +BSS.IO.saveMolecules("apo_aldose_reductase_v47i", solvated_system, ["gro87", "grotop"]) |
| 225 | +``` |
| 226 | + |
| 227 | + |
| 228 | +```python |
| 229 | +# export the solvated system to SOMD2 input file |
| 230 | +BSS.Stream.save(solvated_system, "apo_aldo_reductase_v47i") |
| 231 | +``` |
| 232 | + |
| 233 | +## Holo System |
| 234 | + |
| 235 | +To set up a holo (bound) system, we are going to load in the associated ligand and the cofactor of aldose reductase: |
| 236 | + |
| 237 | + |
| 238 | +```python |
| 239 | +ligand_47d = BSS.IO.readMolecules(BSS.IO.expand(BSS.tutorialUrl(), ["ligand_47_gaff2.gro", "ligand_47_gaff2.top"]))[0] |
| 240 | +cofactor_nap = BSS.IO.readMolecules(BSS.IO.expand(BSS.tutorialUrl(), ["cofactor_nap_gaff2.gro", "cofactor_nap_gaff2.top"]))[0] |
| 241 | +``` |
| 242 | + |
| 243 | +We can use BioSimSpace's Amber parametrisation pipeline if we wish to, but in this case the ligands have been parametrised for us so we can skip the following cell. If you uncomment and run this cell it may take several minutes to complete. |
| 244 | + |
| 245 | + |
| 246 | +```python |
| 247 | +# ligand_47d = BSS.Parameters.gaff2(ligand_47d, charge_method="BCC", net_charge=-1).getMolecule() |
| 248 | +# cofactor_nap = BSS.Parameters.gaff2(cofactor_nap, charge_method="BCC", net_charge=-4).getMolecule() |
| 249 | +``` |
| 250 | + |
| 251 | +We can simply add the ligands to our alchemical protein in order to create an alchemical holo system. This way we are assuming that the ligands are already placed correctly with respect to the protein: |
| 252 | + |
| 253 | + |
| 254 | +```python |
| 255 | +merged_system = merged_protein + ligand_47d + cofactor_nap |
| 256 | +``` |
| 257 | + |
| 258 | +As before we can now proceed to solvate, ionise and export our prepared system or use BioSimSpace's functionallity to [further set up and execute the alchemical simulations](https://github.com/OpenBioSim/biosimspace_tutorials/tree/main/04_fep). |
| 259 | + |
| 260 | + |
| 261 | +```python |
| 262 | +padding = 15 * BSS.Units.Length.angstrom |
| 263 | + |
| 264 | +box_min, box_max = merged_system.getAxisAlignedBoundingBox() |
| 265 | +box_size = [y - x for x, y in zip(box_min, box_max)] |
| 266 | +box_sizes = [x + padding for x in box_size] |
| 267 | + |
| 268 | +box, angles = BSS.Box.rhombicDodecahedronHexagon(max(box_sizes)) |
| 269 | +solvated_system = BSS.Solvent.tip3p(molecule=merged_system, box=box, angles=angles, ion_conc=0.15) |
| 270 | + |
| 271 | +BSS.IO.saveMolecules("holo_aldose_reductase_v47i", solvated_system, ["gro87", "grotop"]) |
| 272 | +``` |
0 commit comments