@@ -198,7 +198,7 @@ figure out the residue index of our residue of interest (ROI):
198198
199199 We can see that the residue with the index value of 8 are different
200200between the two proteins. Let’s pass this value to the
201- `` ` BioSimSpace.Align.matchAtoms`` <https://biosimspace.openbiosim.org/api/generated/BioSimSpace.Align.matchAtoms.html#BioSimSpace.Align.matchAtoms >`__
201+ `BioSimSpace.Align.matchAtoms <https://biosimspace.openbiosim.org/api/generated/BioSimSpace.Align.matchAtoms.html >`__
202202function:
203203
204204.. code :: ipython3
@@ -398,3 +398,169 @@ simulations <https://github.com/OpenBioSim/biosimspace_tutorials/tree/main/04_fe
398398 solvated_system = BSS.Solvent.tip3p(molecule=merged_system, box=box, angles=angles, ion_conc=0.15)
399399
400400 BSS.IO.saveMolecules("holo_aldose_reductase_v47i", solvated_system, ["gro87", "grotop"])
401+
402+ Advanced Case - Bond Creation/Annihilation Transformations
403+ ==========================================================
404+
405+ In this tutorial we will use BioSimSpace’s mapping functionality to set
406+ up alchemical calculations involving proline mutations in a protein.
407+ Specifically, we will look at the Leu-to-Pro mutations in OMTKY3 to its
408+ receptors as `detailed in this
409+ study <https://pubs.acs.org/doi/full/10.1021/acs.jctc.1c00214> `__.
410+
411+ .. code :: ipython3
412+
413+ wt = BSS.IO.readMolecules(
414+ BSS.IO.expand(BSS.tutorialUrl(), f"1choFH_apo_wt_flare_processed.pdb")
415+ )[0]
416+ mut = BSS.IO.readMolecules(
417+ BSS.IO.expand(BSS.tutorialUrl(), f"1choFH_apo_mut_flare_processed.pdb")
418+ )[0]
419+
420+ wt = BSS.Parameters.ff14SB(wt, ensure_compatible=False).getMolecule()
421+ mut = BSS.Parameters.ff14SB(mut, ensure_compatible=False).getMolecule()
422+
423+
424+
425+ Comparing the residues between two proteins shows us that the residues
426+ at index 15 are different between the proteins
427+
428+ .. code :: ipython3
429+
430+ roi = []
431+ for i, res in enumerate(wt.getResidues()):
432+ if res.name() != mut.getResidues()[i].name():
433+ print(res, mut.getResidues()[i])
434+ roi.append(res.index())
435+
436+
437+ .. parsed-literal ::
438+
439+ <BioSimSpace.Residue: name='LEU', molecule=5, index=15, nAtoms=19> <BioSimSpace.Residue: name='PRO', molecule=7, index=15, nAtoms=14>
440+
441+
442+ By default, the
443+ `BioSimSpace.Align.matchAtoms <https://biosimspace.openbiosim.org/api/generated/BioSimSpace.Align.matchAtoms.html >`__
444+ would fail to create a mapping for the ROI region, as the underlying
445+ RDKit MCS algorithm would be unable to determine a mapping between two
446+ molecular graphs with a fundamental topological mismatch. Because
447+ Proline’s sidechain forms a cyclic system with the backbone, its atoms
448+ exist in a ring topology. The algorithm restricts the mapping of cyclic
449+ atoms to acyclic atoms (a behavior governed by parameters like
450+ ``ringMatchesRingOnly ``) to preserve the chemical integrity of the
451+ substructure. Consequently, a 1:1 mapping between the ring-bound
452+ sidechain of Proline and the acyclic sidechain of the Leucine residue
453+ cannot be determined.
454+
455+ Instead we can use the ``custom_roi_map `` argument of the
456+ `BioSimSpace.Align.matchAtoms <https://biosimspace.openbiosim.org/api/generated/BioSimSpace.Align.matchAtoms.html >`__
457+ to override the RDKit MCS mapping. For example we can force an empty
458+ mapping between the two residues:
459+
460+ .. code :: ipython3
461+
462+ mapping = BSS.Align.matchAtoms(molecule0=wt, molecule1=mut, roi=[15], custom_roi_map={})
463+
464+ .. code :: ipython3
465+
466+ BSS.Align.viewMapping(wt, mut, mapping, roi=15, pixels=500)
467+
468+
469+
470+ .. image :: images/custom_roi_no_map.png
471+ :width: 800
472+
473+
474+ If we know the correct 1:1 atom mapping between the two residues, we can
475+ pass that to the ``custom_roi_map `` which will allows us to setup an
476+ alchemical bond transformation for mutating the leucine residue to
477+ proline. Note that **absolute atom indices need to be passed, i.e the
478+ indices of the residues in the context of the whole protein **. We can
479+ use something like PyMol to help us map the atoms in the right order:
480+
481+ .. image :: images/1choFH_mapping_pymol.png
482+ :width: 800
483+
484+ .. code :: ipython3
485+
486+ mapping = BSS.Align.matchAtoms(molecule0=wt, molecule1=mut, roi=[15], custom_roi_map={204:204,205:205,203:203,202:202,211:208,206:206,213:210,207:207,214:211,210:213})
487+
488+ .. code :: ipython3
489+
490+ BSS.Align.viewMapping(wt, mut, mapping, roi=15, pixels=500)
491+
492+
493+
494+ .. image :: images/custom_roi_ring_break_map.png
495+ :width: 800
496+
497+
498+ We can then use ``allow_ring_breaking=True `` argument of the
499+ `BioSimSpace.Align.merge <https://biosimspace.openbiosim.org/api/generated/BioSimSpace.Align.merge.html >`__
500+ to create the required alchemical transformation:
501+
502+ .. code :: ipython3
503+
504+ aligned_wt = BSS.Align.rmsdAlign(molecule0=wt, mapping=mapping, molecule1=mut)
505+ merged_protein = BSS.Align.merge(aligned_wt, mut, mapping, allow_ring_breaking=True, roi=[15])
506+
507+ But how do we actually know that the merge has built a perturbable
508+ molecule that now has a bond annihilation or creation involved? We can
509+ use Sire’s conversion features to check what kinds of alchemical
510+ modifications are happening in our perturbable molecule. You can check
511+ out the corresponding `Sire
512+ tutorial <https://sire.openbiosim.org/tutorial/part07/04_merge.html> `__
513+ for more details.
514+
515+ .. code :: ipython3
516+
517+ merged_protein_sire = merged_protein._sire_object
518+ pert = merged_protein_sire.perturbation()
519+ pert_omm = pert.to_openmm(map={"coordinates":"coordinates0"})
520+
521+ pert_omm.changed_bonds(to_pandas=True)
522+
523+
524+
525+
526+
527+ .. raw :: html
528+
529+ <div >
530+ <table border =" 1" class =" dataframe" >
531+ <thead >
532+ <tr style =" text-align : right ;" >
533+ <th ></th >
534+ <th >bond</th >
535+ <th >length0</th >
536+ <th >length1</th >
537+ <th >k0</th >
538+ <th >k1</th >
539+ </tr >
540+ </thead >
541+ <tbody >
542+ <tr >
543+ <th >0</th >
544+ <td >N:203-H:211</td >
545+ <td >0.1010</td >
546+ <td >0.1449</td >
547+ <td >363171.2</td >
548+ <td >282001.6</td >
549+ </tr >
550+ <tr >
551+ <th >1</th >
552+ <td >CG:208-H:211</td >
553+ <td >0.1526</td >
554+ <td >0.1526</td >
555+ <td >0.0</td >
556+ <td >259408.0</td >
557+ </tr >
558+ </tbody >
559+ </table >
560+ </div >
561+
562+
563+
564+ By comparing the ``k0 `` and ``k1 `` values in the changed bond dataframe,
565+ we can see that the transformation is going to result in a bond being
566+ created.
0 commit comments