Source code for schrodinger.application.bioluminate.protein_protein_docking_shared

# Module for components shared between the protein-protein docking GUI
# and the backend

import json

from schrodinger.structure import Structure
from schrodinger.structutils import analyze

NUMBER_OF_ROTATIONS = 70000
MAX_NUMBER_OF_ROTATIONS = 70000
MULTIMER_ROTATIONS = 9994

EXAMPLE_CONSTRAINTS_FILE = """
[
    {
        "constraint_type" : "attraction",
        "asl" : "res.num 2-3",
        "protein_type"    : "receptor",
        "attraction"      : 2.0
    },
    {
        "constraint_type" : "repulsion",
        "asl" : "res.num 1-2",
        "protein_type"    : "ligand"
    },
    {
        "constraint_type" : "distance",
        "required": 1,
        "distance_pairs": [
            {
                "lig_asl" : "res.num 1",
                "rec_asl" : "res.num 4",
                "dmax" : 4.0,
                "dmin" : 1.0
            },
            {
                "lig_asl" : "res.num 3",
                "rec_asl" : "res.num 1",
                "dmax" : 5.0,
                "dmin" : 1.5
            }
       ]
    }
]
"""


[docs]def get_residues_for_asl(st, asl): """ For a given input structure, st {schrodinger.Structure} and an asl expressions that defines a residue subset, asl {string}, return the list of residues that are in the asl expression. This will be a list of tuples contraining the chain name {string} and the combination of residue number and inscode {string} of each residue Note: list is sorted to create more reproducible results """ residues = set() for idx in analyze.evaluate_asl(st, asl): atom = st.atom[idx] # Note - I suspect this PIPER code will break for # Note - I suspect this PIPER code will break for # chain name = " " - DJG if atom.inscode == " ": residue = (atom.chain, "%s" % (atom.resnum)) else: residue = (atom.chain, "%s%s" % (atom.resnum, atom.inscode)) residues.add(residue) return sorted(list(residues))
[docs]class PIPERDistanceConstraintPair(object): """ Object used to store the pairs of residues and the allowable distaances between them that make up a piper distance constraint. """
[docs] def __init__(self, rec_asl: str = None, lig_asl: str = None, dmin: float = 0.0, dmax: float = 5.0): """ Initialize with: :param rec_asl: REQUIRED ASL espression corresponding to exactly one residue on the receptor :param lig_asl: REQUIRED ASL espression corresponding to exactly one residue on the ligand :param dmin: minimum allowed distance between any two atoms on the residues defined by rec_asl and lig_asl :param dmax: maximum allowed distance between any two atoms on the residues defined by rec_asl and lig_asl """ self.rec_asl = rec_asl self.lig_asl = lig_asl self.dmin = dmin self.dmax = dmax if self.rec_asl is None or self.lig_asl is None: raise RuntimeError("lig_asl and rec_asl must be defined for " "PIPERDistanceConstraintPair")
def __str__(self): return "Lig %s -- Rec %s Distance (%5.2f - %5.2f)" % ( self.lig_asl, self.rec_asl, self.dmin, self.dmax)
[docs] def toDict(self): return { 'rec_asl': self.rec_asl, 'lig_asl': self.lig_asl, 'dmin': self.dmin, 'dmax': self.dmax }
[docs] def setResidues(self, rec_st: Structure, lig_st: Structure): """ This must be called to set link the asl expresion given in the initializer to a set of structures :param rec_st: receptor structure :param lig_st: receptor structure If the ASL expression provided in the initializer does not correspond to exactly one residue in rec_st and lig_st respectively than a RuntimeError is raised """ self.rec_chain, self.rec_resi = self._setOneComponent( "Receptor", rec_st, self.rec_asl) self.lig_chain, self.lig_resi = self._setOneComponent( "Ligand", lig_st, self.lig_asl)
def _setOneComponent(self, name, st, asl): residues = get_residues_for_asl(st, asl) if len(residues) != 1: raise RuntimeError("%s ASL Expression must correspond to " % name + "exactly 1 residue: %s -> %s " % (asl, ",".join(residues))) return residues[0][0], residues[0][1]
[docs] def getPiperDict(self): """ Return the dictionary that will be used to create the piper-backend-formatted json file. This format is different than the json format used to pass constraint objects to the schrodinger-wrapper for Prime as that json file must include distance and energy-based constraints. """ try: return { "rec_chain": self.rec_chain, "rec_resi": self.rec_resi, "lig_chain": self.lig_chain, "lig_resi": self.lig_resi, "dmin": self.dmin, "dmax": self.dmax } except AttributeError as err: raise AttributeError("Must call setResidues. (%s)" % err)
[docs]class PIPERConstraint(object): """ Container for managing constraints """ ATTRACTION = 'attraction' REPULSION = 'repulsion' DISTANCE = 'distance' RECEPTOR = 'receptor' LIGAND = 'ligand' ANTIBODY = 'antibody' ANTIGEN = 'antigen'
[docs] def __init__(self, constraint_type=None, asl=None, protein_type=None, attraction=None, required=None, distance_pairs=None): """ """ self.constraint_type = constraint_type if self.isEnergy(): self.asl = asl self.protein_type = protein_type self.attraction = attraction self.at_surface = (attraction == 0.0) """ Property holding the list of strings that can be added to a command line list to identify residues in attraction/masking scripts. """ self.commandline_residues = [] elif self.isDistance(): self.distance_pairs = [ PIPERDistanceConstraintPair(**pair) for pair in distance_pairs ] self.required = required assert (type(self.required) == int) else: raise NotImplementedError("Incorrect constraint_type for " "PIPERConstraint: %s" % self.constraint_type)
[docs] def isEnergy(self): """ Return True if this is an ENERGY-based constraint""" return self.constraint_type in (self.ATTRACTION, self.REPULSION)
[docs] def isDistance(self): """ Return True if this is an DISTANCE-based constraint""" return self.constraint_type in (self.DISTANCE)
def __str__(self): if self.isEnergy(): if self.attraction is None: return "Constraint %s %s %s" % (self.protein_type, self.constraint_type, self.asl) else: return "Constraint %s %s(%5.3f) %s" % ( self.protein_type, self.constraint_type, self.attraction, self.asl) elif self.isDistance(): return "Constraint %s (%d required) ( %s )" % ( self.constraint_type, self.required, ",".join( ["(%s)" % p for p in self.distance_pairs]))
[docs] def setResidues(self, rec_st, lig_st): """ Uses `self.asl` with `st_file` and to set the commandline_residues property. """ if self.isEnergy(): # If protein_type is receptor OR we are running in multimer mode # we need to use the receptor to later setResidues if (self.protein_type in (PIPERConstraint.RECEPTOR, PIPERConstraint.ANTIBODY)): residues = get_residues_for_asl(rec_st, self.asl) else: residues = get_residues_for_asl(lig_st, self.asl) self.commandline_residues = ["%s-%s" % (c, r) for c, r in residues] elif self.isDistance(): for pair in self.distance_pairs: pair.setResidues(rec_st, lig_st)
[docs] def getPiperDict(self): """ Return the dictionary that will be used to create the piper-backend-formatted json file. This format is different than the json format used to pass constraint objects to the schrodinger-wrapper for Prime as that json file must include This is used only for the distance constraints ( energy constraints are passed to piper using command-line options) so it will raise a NotImplementedError if used with an energy constraint """ if not self.isDistance(): raise NotImplementedError("Only Distance Constraints Need to " "be written as Piper JSon Objects") return { 'required': self.required, 'restraints': [pair.getPiperDict() for pair in self.distance_pairs] }
[docs] @staticmethod def read_constraints_file(fname): """ Reads a constraints file and returns the python representation as loaded from the json module. """ with open(fname) as fh: return json.load(fh)
[docs] @staticmethod def write(constraints, ofile): """ Writes a list of constraints to a JSON formatted file. """ json_obj = [] for constraint in constraints: if constraint.isEnergy(): json_obj.append({ 'asl': constraint.asl, 'constraint_type': constraint.constraint_type, 'protein_type': constraint.protein_type, 'attraction': constraint.attraction }) elif constraint.isDistance(): json_obj.append({ 'required': constraint.required, 'constraint_type': constraint.constraint_type, 'distance_pairs': [ pair.toDict() for pair in constraint.distance_pairs ] }) with open(ofile, 'w') as fh: return json.dump(json_obj, fh, indent=4)