Exemplo n.º 1
0
    def prepare(self):
        """
        """
        mrec = self.com.rec()
        mlig = self.com.lig()

        m = mrec.concat(mlig)

        if self.protonate:
            if self.verbose:
                self.log.add('\nRe-building hydrogen atoms...')

            tempdir = self.tempdir
            if tempdir:
                tempdir += '/0_reduce'

            r = Reduce(m,
                       tempdir=tempdir,
                       log=self.log,
                       autocap=False,
                       debug=self.debug,
                       verbose=self.verbose)
            m = r.run()

        if self.addcharge:
            if self.verbose:
                self.log.add('\nAssigning charges from Amber topologies...')

            ac = AtomCharger(log=self.log, verbose=self.verbose)
            ac.charge(m)

        mrec = m.takeChains(range(mrec.lenChains()))
        mlig = m.takeChains(range(mrec.lenChains(), m.lenChains()))

        self.delphicom = Complex(mrec, mlig)
Exemplo n.º 2
0
    def random_complex( self, inp_mirror=None ):
        """
        @return: randomized and minimized complex
        @rtype: Complex
        """
        self.cm = ComplexMinimizer( self.random_complex_remote(),
                                    debug=self.debug )
        self.cm.run( inp_mirror=inp_mirror )

        com = Complex( self.rec, self.lig )

        rt = com.extractLigandMatrix( self.cm.lig )
        com.setLigMatrix( rt )

        return com
Exemplo n.º 3
0
    def prepare( self ):
        """
        """
        mrec = self.com.rec()
        mlig = self.com.lig()
        
        m = mrec.concat( mlig )

        if self.protonate:
            if self.verbose:
                self.log.add( '\nRe-building hydrogen atoms...' )
            
            tempdir = self.tempdir
            if tempdir:
                tempdir += '/0_reduce'
                
            r = Reduce( m, tempdir=tempdir, log=self.log,
                        autocap=False,
                        debug=self.debug, verbose=self.verbose )
            m = r.run()
            
        if self.addcharge:
            if self.verbose:
                self.log.add( '\nAssigning charges from Amber topologies...')
                    
            ac = AtomCharger( log=self.log, verbose=self.verbose )
            ac.charge( m )
            
        mrec = m.takeChains( range( mrec.lenChains() ) )
        mlig = m.takeChains( range( mrec.lenChains(), m.lenChains() ) )
            
        self.delphicom = Complex( mrec, mlig )
Exemplo n.º 4
0
    def toComplex( self, copy=0 ):
        """
        Copy of latest version as a normal Complex.

        @param copy: also disconnect info dict (default: 0)
        @type  copy: 1|0

        @return: Complex
        @rtype: Complex
        """
        r = ProtComplex( self.rec_model, self.lig_model,
                         self.ligandMatrix, self.info )
        if not copy:
            r.info = self.info

        return r
Exemplo n.º 5
0
    def toComplex(self, copy=0):
        """
        Copy of latest version as a normal Complex.

        @param copy: also disconnect info dict (default: 0)
        @type  copy: 1|0

        @return: Complex
        @rtype: Complex
        """
        r = ProtComplex(self.rec_model, self.lig_model, self.ligandMatrix,
                        self.info)
        if not copy:
            r.info = self.info

        return r
Exemplo n.º 6
0
    def random_complex_remote( self ):
        """
        Create a complex where the recrptor and ligand have random
        orientations but are spaced within contact distance.

        @return: rec & lig spaced r_rec + r_lig apart in random orientation
        @rtype: Complex
        """
        return Complex( self.rec, self.lig,
                        ligMatrix= self.__random_matrix() )
Exemplo n.º 7
0
    def __init__(self,
                 rec_model,
                 lig_model,
                 com_0=None,
                 ligMatrix=None,
                 info={}):
        """
        Create a new ComplexVC from a previous Complex or ComplexVC
        and a new set of receptor, ligand conformation and transformation.

        @param rec_model: PDBModel/XplorModel, receptor conformation
        @type  rec_model: PDBModel
        @param lig_model: PDBModel/XplorModel, ligand conformation
        @type  lig_model: PDBModel
        @param com_0: Complex /ComplexVC, previous version(s) of this com
        @type  com_0: Complex OR ComplexVC
        @param ligMatrix: transformation matrix of ligand versus receptor
        @type  ligMatrix: 4x4 array
        @param info: info dictionary {'info_key': value, ..}, additional infos
        @type  info: dict
        """
        ProtComplex.__init__(self, rec_model, lig_model, ligMatrix, info)

        if isinstance(com_0, ComplexVC):
            ## get history from mother complex ...
            self.history = com_0.history

            com_0 = ProtComplex(com_0.rec_model, com_0.lig_model,
                                com_0.ligandMatrix, com_0.info)

        else:
            ## ... or create a new history
            self.history = ComplexList()

        if com_0 is not None:
            self.history.append(com_0)

            ## save only differences between old and new conformations
            self.rec_model = self.__syncModel(self.rec_model, com_0.rec_model)
            self.lig_model = self.__syncModel(self.lig_model, com_0.lig_model)
Exemplo n.º 8
0
    def prepareRef( self, fname ):
        """
        Prepare reference model.
        
        :param fname: file name 
        :type  fname: str

        :return: reference structure
        :rtype: PDBModel|Complex        

        :raise EntropistError: if unknown reference type
        """
        if not fname:
            return None

        if self.__splitFilenames( fname ):
            f1, f2 = self.__splitFilenames( fname )
            m1, m2 = PDBModel( self.__getModel(f1) ), \
                     PDBModel( self.__getModel(f2) )

            ref = Complex( m1, m2 )
        else:
            ref = t.load( fname )

        if isinstance( ref, Trajectory ):
            ref = ref.ref

        if isinstance( ref, PDBModel ):
            return self.__cleanAtoms( ref )

        if isinstance( ref, Complex ):
            self.__cleanAtoms( ref.rec_model )
            self.__cleanAtoms( ref.lig_model )
            ref.lig_model_transformed = None
            return ref

        raise EntropistError('unknown reference type')
Exemplo n.º 9
0
    def prepareRef(self, fname):
        """
        Prepare reference model.
        
        :param fname: file name 
        :type  fname: str

        :return: reference structure
        :rtype: PDBModel|Complex        

        :raise EntropistError: if unknown reference type
        """
        if not fname:
            return None

        if self.__splitFilenames(fname):
            f1, f2 = self.__splitFilenames(fname)
            m1, m2 = PDBModel( self.__getModel(f1) ), \
                     PDBModel( self.__getModel(f2) )

            ref = Complex(m1, m2)
        else:
            ref = t.load(fname)

        if isinstance(ref, Trajectory):
            ref = ref.ref

        if isinstance(ref, PDBModel):
            return self.__cleanAtoms(ref)

        if isinstance(ref, Complex):
            self.__cleanAtoms(ref.rec_model)
            self.__cleanAtoms(ref.lig_model)
            ref.lig_model_transformed = None
            return ref

        raise EntropistError('unknown reference type')
Exemplo n.º 10
0
    def __init__(self, rec_model, lig_model, com_0=None,
                 ligMatrix=None, info={} ):
        """
        Create a new ComplexVC from a previous Complex or ComplexVC
        and a new set of receptor, ligand conformation and transformation.

        @param rec_model: PDBModel/XplorModel, receptor conformation
        @type  rec_model: PDBModel
        @param lig_model: PDBModel/XplorModel, ligand conformation
        @type  lig_model: PDBModel
        @param com_0: Complex /ComplexVC, previous version(s) of this com
        @type  com_0: Complex OR ComplexVC
        @param ligMatrix: transformation matrix of ligand versus receptor
        @type  ligMatrix: 4x4 array
        @param info: info dictionary {'info_key': value, ..}, additional infos
        @type  info: dict
        """
        ProtComplex.__init__(self, rec_model, lig_model, ligMatrix, info )

        if isinstance( com_0, ComplexVC ):
            ## get history from mother complex ...
            self.history = com_0.history

            com_0 = ProtComplex( com_0.rec_model, com_0.lig_model,
                                 com_0.ligandMatrix, com_0.info )

        else:
            ## ... or create a new history
            self.history = ComplexList()

        if com_0 is not None:
            self.history.append( com_0 )

            ## save only differences between old and new conformations
            self.rec_model = self.__syncModel( self.rec_model, com_0.rec_model)
            self.lig_model = self.__syncModel( self.lig_model, com_0.lig_model)
Exemplo n.º 11
0
    def test_errorcase1(self):
        """bindinEnergyDelphi test (error case 01)"""
        self.m = PDBModel(T.testRoot() + '/delphi/case01.pdb')
        rec = self.m.takeChains([0, 1])
        lig = self.m.takeChains([2])
        self.com = Complex(rec, lig)

        self.dG = DelphiBindingEnergy(self.com,
                                      log=self.log,
                                      scale=0.5,
                                      verbose=self.local)
        self.r = self.dG.run()

        if self.local:
            self.log.add('\nFinal result: dG = %3.2f kcal/mol' %
                         self.r['dG_kcal'])
Exemplo n.º 12
0
    def prepareTraj( self, fname, ref=None, cast=1 ):
        """
        Prepare trajectory for Amber.

        :param fname: path to EnsembleTraj OR ( EnsembleTraj, EnsembleTraj )
        :type  fname: str OR (str,str)
        :param ref: reference structure
        :type  ref: EnsembleTraj
        :param cast: cast to reference (same atom content) (default: 1)
        :type  cast: 1|0

        :return: split, fitted or shuffled, etc. trajectory instance
        :rtype: EnsembleTraj OR (EnsembleTraj, EnsembleTraj )
        """
        ## Load 1 or 2
        if self.__splitFilenames( fname ):
            f1, f2 = self.__splitFilenames( fname )
            t = self.loadTraj( f1 ), self.loadTraj( f2 )
        else:
            t = self.loadTraj( fname )
            if self.chains:
                t = t.takeChains( self.chains )

        ## split 1 into 2 if necessary
        if not type(t) is tuple and self.border:
            lig = range( self.border, t.ref.lenChains() )
            t = t.takeChains( range(self.border) ), t.takeChains( lig )

        ## check 2 trajectories were suplied
        if not type(t) is tuple and (self.shift or self.shuffle or self.split):
            raise EntropistError('split,shift,shuffle require -border.')

        ## adapt reference to type of trajectory input 
        if ref and type(t) is tuple and not isinstance( ref, Complex ):
            rec = ref.takeChains( range(t[0].ref.lenChains()) )
            lig = ref.takeChains( range(t[0].ref.lenChains(),
                                        t[1].ref.lenChains()) )
            ref = Complex( rec, lig )

        if ref and type(t) is not tuple and isinstance( ref, Complex ):
            ref = ref.rec_model.concat( ref.lig() )

        ## remove member trajectories (if requested)
        t = self.__removeMembers( t )

        ## cast 1 or 2
        if cast and type( t ) is not tuple:
            self.castTraj( t, ref )

        if cast and type( t ) is tuple:
            self.castTraj( t[0], ref.rec_model )
            self.castTraj( t[1], ref.lig_model )

        ## reorder one half (requires -border or file name pair )
        if self.shift:
            t = self.shiftTraj( t[0], self.shift ), t[1]

        if self.shuffle:
            t = self.shuffleTraj( t[0] ), self.shuffleTraj( t[1] )

        ## fit seperately (requires -border or file name pair)
        if self.split and ref:
            self.fit( t[0], ref.rec() )
            self.fit( t[1], ref.lig() )
        if self.split and not ref:
            self.fit( t[0] )
            self.fit( t[1] )

        if type( t ) is tuple:
            t = t[0].concatAtoms( t[1] )
            ref = ref.rec_model.concat( ref.lig() )

        ## joint fit
        if not self.split:
            self.fit( t, ref )
            
        if self.verbose:
            self.log.add( 'Analysing trajectory with %i atoms and %i frames.' \
                          % (t.lenAtoms(), t.lenFrames()))

        return t
Exemplo n.º 13
0
class DelphiBindingEnergy( object ):
    """
    Determine the electrostatic component of the free energy of binding using
    several rounds of Delphi calculations. DelphiBindingEnergy accepts a 
    binary complex (L{Biskit.Dock.Complex}) as input, performs several house
    keeping tasks (optional capping of free terminals, h-bond optimization and
    protonation with the reduce program, assignment of Amber partial atomic 
    charges) and then calls Delphi six times:

    1 - 3: one run each for complex, receptor, and ligand with zero ionic 
           strength
    4 - 6: one run each for complex, receptor, and ligand with custom salt
           concentration (default: physiological 0.15 M).
           
    The grid position and dimensions are determined once for the complex and
    then kept constant between all runs so that receptor, ligand and complex
    calculations can indeed be compared to each other.
           
    The free energy of binding is calculated as described in the Delphi
    documentation -- see: delphi2004/examples/binding.html -- according to the
    energy partioning scheme:
    
    dG = dG(coulomb) + ddG(solvation) + ddG(ions)
    Please have a look at the source code of DelphiBindingEnergy.bindingEnergy() 
    for a detailed breakup of these terms.
    
    Use:
    ====
    
    >>> com = Biskit.Complex( 'myrec.pdb', 'mylig.pdb' )
    >>> dg = DelphiBindingEnergy( com, verbose=True )
    >>> r = dg.run()

    Variable r will then receive a dictionary with the free energy of binding
    and its three components:
    
         {'dG_kt': free E. in units of kT, 
          'dG_kcal': free E. in units of kcal / mol, 
          'dG_kJ': free E. in units of kJ / mol,
          'coul': coulomb contribution in kT , 
          'solv': solvation contribution in kT, 
          'ions': salt or ionic contribution in kT }
          
    The modified complex used for the calculation (with hydrogens added and
    many other small changes) can be recovered from the DelphiBindingEnergy
    instance:
    
    >>> modified_com = dg.delphicom
    
    The run() method assigns the result dictionary to the info record
    of both the original and the modified complex. That means:
    
    >>> r1 = com.info['dG_delphi']
    >>> r2 = modified_com.info['dG_delphi']
    >>> r1 == r2
    True
    
    The result of the original DelPhi calculations (with and without salt for
    receptor, ligand and complex) are assigned to the info dictionaries of the
    complex' receptor and ligand model as well as to the complex itself:
    
    com.info['delphi_0.15salt'] ...delphi run with 0.15M salt on complex
    com.info['delphi_0salt']    ...delphi run without salt on complex

    com.lig().info['delphi_0.15salt'] ...delphi run with 0.15M salt on ligand
    com.lig().info['delphi_0salt']    ...delphi run without salt on ligand
    com.rec().info['delphi_0.15salt'] ...delphi run with 0.15M salt on receptor
    com.rec().info['delphi_0salt']    ...delphi run without salt on receptor
    
    From there the individual values for solvation, ionic and couloumb
    contributions can be recovered. See L{Biskit.Delphi} for a description of
    this result dictionary.
     
    Customization:
    ==============
    
    The most important Delphi parameters (dielectric, salt concentration, 
    grid scales, ion and probe radius) can be adjusted by passing parameters
    to the constructor (see documentation of __init__). The default parameters
    should be reasonable. By default,
    we create a grid that covers every linear dimension to at least 60% 
    (perfil=60) and has a density of 2.3 points per Angstroem (scale=2.3). 
    Such high densities come at much larger computational cost. It is
    recommended to test different values and average results.
    
    Note: Any parameters that are not recognized by 
    DelphiBindingEnergy() will be passed on to the Biskit.Delphi instance
    of each Delphi run and, from there, passed on to Biskit.Executor.
    
    The internal pipeline of DelphiBindingEnergy consists of:
    
    * adding hydrogens and capping of protein chain breaks and terminals with
      Biskit.Reduce( autocap=True ). The capping is performed by 
      L{Biskit.PDBCleaner} called from within Reduce.
    
    * mapping Amber partial atomic charges into the structure with 
      Biskit.AtomCharger()
      
    * setting up the various delphi runs with L{Biskit.Delphi}.
    
    A point worth looking at is the automatic capping of protein chain
    breaks and premature terminals with ACE and NME residues. This should
    generally be a good idea but the automatic discovery of premature C-termini
    or N-termini is guess work at best. See L{Biskit.PDBCleaner} for a more
    detailed discussion. You can override the default behaviour by setting
    autocap=False (no capping at all, this is now the default) and you can 
    then provide a complex structure that is already pre-treated by 
    L{Biskit.Reduce}.
    For example:
    
    >>> m = PDBModel('mycomplex.pdb')
    >>> m = Reduce( m, capN=[0], capC=[2] )
    >>> com = Biskit.Complex( m.takeChains([0]), m.takeChains([1,2]) )
    >>> dg = DelphiBindingEnergy( com, protonate=False )
    
    In this case, the original structure would receive a ACE N-terminal
    capping of the first chain and the second chain would receive a C-terminal
    NME capping residue. The first chain is considered receptor and the second
    and third chain are considered ligand.
    
    @see: L{Biskit.PDBCleaner}
    @see: L{Biskit.Reduce}
    @see: L{Biskit.AtomCharger}
    @see: L{Biskit.Delphi}
    """
    
    def __init__(self, com, 
                 protonate=True, addcharge=True,
                 indi=4.0, exdi=80.0, salt=0.15, ionrad=2, prbrad=1.4, 
                 bndcon=4, scale=2.3, perfil=60, 
                 template=None, topologies=None,
                 f_charges=None,
                 verbose=True, debug=False, log=None, tempdir=None, cwd=None,
                 **kw ):
        """
        @param com: complex to analyze
        @type  com: Biskit.Complex
        @param protonate: (re-)build hydrogen atoms with reduce program (True)
                          see L{Biskit.Reduce}
        @type  protonate: bool
        @param addcharge: Assign partial charges from Amber topologies (True)
        @type  addcharge: bool
        @param indi: interior dilectric (4.0)
        @param exdi: exterior dielectric (80.0)
        @param salt: salt conc. in M (0.15)
        @param ionrad: ion radius (2)
        @param prbrad: probe radius (1.4) 
        @param bndcon: boundary condition (4, delphi default is 2)
        @param scale:  grid spacing (2.3)
        @param perfil: grid fill factor in % (for automatic grid, 60) 
        
        @param template: delphi command file template [None=use default]
        @type  template: str
        @param f_radii: alternative delphi atom radii file [None=use default]
        @type  f_radii: str
        @param topologies: alternative list of residue charge/topology files
                           [default: amber/residues/all*]
        @type  topologies: [ str ]
        @param f_charges: alternative delphi charge file 
                          [default: create custom]
        @type  f_charges: str

        @param kw: additional key=value parameters for Delphi or Executor:
        @type  kw: key=value pairs
        ::
          debug    - 0|1, keep all temporary files (default: 0)
          verbose  - 0|1, print progress messages to log (log != STDOUT)
          node     - str, host for calculation (None->local) NOT TESTED
                          (default: None)
          nice     - int, nice level (default: 0)
          log      - Biskit.LogFile, program log (None->STOUT) (default: None)
        """
        self.com = com
        self.delphicom = None
        
        self.protonate = protonate
        self.addcharge = addcharge
        ## DELPHI run parameters
        self.indi=indi  # interior dilectric(4.0)
        self.exdi=exdi  # exterior dielectric(80.0)
        self.salt=salt  # salt conc. in M (0.15)
        self.ionrad=ionrad # ion radius (2)
        self.prbrad=prbrad # probe radius (1.4) 
        self.bndcon=bndcon # boundary condition (4, delphi default is 2)
        
        ## DELPHI parameters for custom grid
        self.scale=scale   # grid spacing (1.2 / A)
        self.perfil=perfil # grid fill factor in % (for automatic grid, 60)
        
        ## DELPHI parameter file and Amber residue definitions or charge file
        self.template = template
        self.topologies = topologies
        self.f_charges = f_charges
        
        ## pump everything else into name space, too
        self.__dict__.update( kw )
        
        ## prepare globally valid grid
        self.grid = None
        
        self.verbose = verbose
        self.log = log or StdLog()
        self.debug = debug
        self.tempdir = tempdir
        self.cwd = cwd
        
        self.ezero = self.esalt = None # raw results assigned by run()

    def prepare( self ):
        """
        """
        mrec = self.com.rec()
        mlig = self.com.lig()
        
        m = mrec.concat( mlig )

        if self.protonate:
            if self.verbose:
                self.log.add( '\nRe-building hydrogen atoms...' )
            
            tempdir = self.tempdir
            if tempdir:
                tempdir += '/0_reduce'
                
            r = Reduce( m, tempdir=tempdir, log=self.log,
                        autocap=False,
                        debug=self.debug, verbose=self.verbose )
            m = r.run()
            
        if self.addcharge:
            if self.verbose:
                self.log.add( '\nAssigning charges from Amber topologies...')
                    
            ac = AtomCharger( log=self.log, verbose=self.verbose )
            ac.charge( m )
            
        mrec = m.takeChains( range( mrec.lenChains() ) )
        mlig = m.takeChains( range( mrec.lenChains(), m.lenChains() ) )
            
        self.delphicom = Complex( mrec, mlig )


    def setupDelphi( self, model, grid={}, **kw ):
        """
        """
        params = copy.copy( self.__dict__ )
        params.update( kw )
        params['protonate'] = False   # run reduce once for complex only
        params['addcharge'] = False   # run AtomCharger once for complex only
        
        d = Delphi( model, **params )
        d.setGrid( **grid )
        
        return d
    
    def processThreesome( self, **kw ):
        """
        Calculate Delphi energies for rec, lig and complex
        @return: result dictionaries for com, rec, lig
        @rtype: ( dict, dict, dict )
        """
        dcom = self.setupDelphi( self.delphicom.model(), **kw )
        grid = dcom.getGrid()
        
        drec = self.setupDelphi( self.delphicom.rec(), grid=grid, **kw )
        dlig = self.setupDelphi( self.delphicom.lig(), grid=grid, **kw )
        
        if self.verbose:
            self.log.add('\nrunning Delphi for complex...')
        r_com = dcom.run()

        if self.verbose:
            self.log.add('\nrunning Delphi for receptor...')
        r_rec = drec.run()
        
        if self.verbose:
            self.log.add('\nrunning Delphi for ligand...')
        r_lig = dlig.run()
        
        return r_com, r_rec, r_lig
    
    def processSixsome( self, **kw ):
        """
        Calculate Delphi energies for rec, lig and complex with and without
        salt.
        @return: com, rec, lig delphi calculations with and without ions
        @rtype: ( {}, {} )
        """
        ## with ions
        if self.verbose:
            self.log.add('\nDelphi calculations with %1.3f M salt' % self.salt)
            self.log.add('=======================================')
        ri_com, ri_rec, ri_lig = self.processThreesome( salt=self.salt, **kw )

        ## w/o ions
        if self.verbose:
            self.log.add('\nDelphi calculations with zero ionic strenght')
            self.log.add('==============================================')
        r_com, r_rec, r_lig = self.processThreesome( salt=0.0, **kw )
        
        rsalt = { 'com':ri_com, 'rec':ri_rec, 'lig':ri_lig }
        rzero = { 'com':r_com, 'rec':r_rec, 'lig':r_lig }
        
        if self.verbose:
            self.log.add('\nRaw results')
            self.log.add('============')
            self.log.add('zero ionic strength: \n' + str(rzero) )
            self.log.add('\nwith salt: \n' + str(rsalt) )
    
        return rzero, rsalt
    
    def bindingEnergy( self, ezero, esalt ):
        """
        Calculate electrostatic component of the free energy of binding 
        according to energy partitioning formula.
        @param ezero: delphi energies calculated at zero ionic strength
        @type  ezero: {'com':{}, 'rec':{}, 'lig':{} }
        @param esalt: delphi energies calculated with ions (see salt=)
        @type  esalt: {'com':{}, 'rec':{}, 'lig':{} }
        @return: {'dG_kt': free E. in units of kT, 
                  'dG_kcal': free E. in units of kcal / mol, 
                  'dG_kJ': free E. in units of kJ / mol,
                  'coul': coulomb contribution in kT , 
                  'solv': solvation contribution in kT, 
                  'ions': salt or ionic contribution in kT }
        @rtype: dict { str : float }
        """
        delta_0 = {}
        delta_i = {}
        for k, v in ezero['com'].items():
            delta_0[ k ] = ezero['com'][k] - ezero['rec'][k] - ezero['lig'][k]
            delta_i[ k ] = esalt['com'][k] - esalt['rec'][k] - esalt['lig'][k]
        
        dG_coul = delta_0['ecoul']
        ddG_rxn = delta_0['erxn']
        ddG_ions= delta_i['egrid'] - delta_0['egrid']
        
        dG = dG_coul + ddG_rxn + ddG_ions  # in units of kT
        dG_kcal = round( dG * 0.593, 3)  # kcal / mol
        dG_kJ = round( dG * 2.479, 3)    # kJ / mol
        
        r = {'dG_kt':dG, 'dG_kcal':dG_kcal, 'dG_kJ':dG_kJ,
             'coul':dG_coul, 'solv':ddG_rxn, 'ions':ddG_ions}
        return r
    
    def run( self ):
        """
        Prepare structures and perform Delphi calculations for complex, 
        receptor and ligand, each with and without ions (salt). Calculate
        free energy of binding according to energy partitioning.
        Detailed delphi results are saved into object fields 'ezero' and
        'esalt' for calculations without and with ions, respectively.
        
        @return: {'dG_kt': free E. in units of kT, 
                  'dG_kcal': free E. in units of kcal / mol, 
                  'dG_kJ': free E. in units of kJ / mol,
                  'coul': coulomb contribution in kT , 
                  'solv': solvation contribution in kT, 
                  'ions': salt or ionic contribution in kT }
        @rtype: dict { str : float }
        """
        self.prepare()
        self.ezero, self.esalt = self.processSixsome()
        self.result = self.bindingEnergy( self.ezero, self.esalt )
        
        self.delphicom.info['dG_delphi'] = self.result
        self.com.info['dG_delphi'] = self.result
        
        for com in [self.delphicom, self.com]:
            key = 'delphi_%4.2fsalt' % self.salt
            com.rec_model.info[key] = self.esalt['rec']
            com.lig_model.info[key] = self.esalt['lig']
            com.lig_model.lig_transformed = None
            key = 'delphi_0salt'
            com.rec_model.info[key] = self.ezero['rec']
            com.lig_model.info[key] = self.ezero['lig']
            com.lig_model.lig_transformed = None  # reset Complex.lig() cache
            com.info[key] = self.ezero['com']
        
        return self.result
Exemplo n.º 14
0
class DelphiBindingEnergy(object):
    """
    Determine the electrostatic component of the free energy of binding using
    several rounds of Delphi calculations. DelphiBindingEnergy accepts a 
    binary complex (L{Biskit.Dock.Complex}) as input, performs several house
    keeping tasks (optional capping of free terminals, h-bond optimization and
    protonation with the reduce program, assignment of Amber partial atomic 
    charges) and then calls Delphi six times:

    1 - 3: one run each for complex, receptor, and ligand with zero ionic 
           strength
    4 - 6: one run each for complex, receptor, and ligand with custom salt
           concentration (default: physiological 0.15 M).
           
    The grid position and dimensions are determined once for the complex and
    then kept constant between all runs so that receptor, ligand and complex
    calculations can indeed be compared to each other.
           
    The free energy of binding is calculated as described in the Delphi
    documentation -- see: delphi2004/examples/binding.html -- according to the
    energy partioning scheme:
    
    dG = dG(coulomb) + ddG(solvation) + ddG(ions)
    Please have a look at the source code of DelphiBindingEnergy.bindingEnergy() 
    for a detailed breakup of these terms.
    
    Use:
    ====
    
    >>> com = Biskit.Complex( 'myrec.pdb', 'mylig.pdb' )
    >>> dg = DelphiBindingEnergy( com, verbose=True )
    >>> r = dg.run()

    Variable r will then receive a dictionary with the free energy of binding
    and its three components:
    
         {'dG_kt': free E. in units of kT, 
          'dG_kcal': free E. in units of kcal / mol, 
          'dG_kJ': free E. in units of kJ / mol,
          'coul': coulomb contribution in kT , 
          'solv': solvation contribution in kT, 
          'ions': salt or ionic contribution in kT }
          
    The modified complex used for the calculation (with hydrogens added and
    many other small changes) can be recovered from the DelphiBindingEnergy
    instance:
    
    >>> modified_com = dg.delphicom
    
    The run() method assigns the result dictionary to the info record
    of both the original and the modified complex. That means:
    
    >>> r1 = com.info['dG_delphi']
    >>> r2 = modified_com.info['dG_delphi']
    >>> r1 == r2
    True
    
    The result of the original DelPhi calculations (with and without salt for
    receptor, ligand and complex) are assigned to the info dictionaries of the
    complex' receptor and ligand model as well as to the complex itself:
    
    com.info['delphi_0.15salt'] ...delphi run with 0.15M salt on complex
    com.info['delphi_0salt']    ...delphi run without salt on complex

    com.lig().info['delphi_0.15salt'] ...delphi run with 0.15M salt on ligand
    com.lig().info['delphi_0salt']    ...delphi run without salt on ligand
    com.rec().info['delphi_0.15salt'] ...delphi run with 0.15M salt on receptor
    com.rec().info['delphi_0salt']    ...delphi run without salt on receptor
    
    From there the individual values for solvation, ionic and couloumb
    contributions can be recovered. See L{Biskit.Delphi} for a description of
    this result dictionary.
     
    Customization:
    ==============
    
    The most important Delphi parameters (dielectric, salt concentration, 
    grid scales, ion and probe radius) can be adjusted by passing parameters
    to the constructor (see documentation of __init__). The default parameters
    should be reasonable. By default,
    we create a grid that covers every linear dimension to at least 60% 
    (perfil=60) and has a density of 2.3 points per Angstroem (scale=2.3). 
    Such high densities come at much larger computational cost. It is
    recommended to test different values and average results.
    
    Note: Any parameters that are not recognized by 
    DelphiBindingEnergy() will be passed on to the Biskit.Delphi instance
    of each Delphi run and, from there, passed on to Biskit.Executor.
    
    The internal pipeline of DelphiBindingEnergy consists of:
    
    * adding hydrogens and capping of protein chain breaks and terminals with
      Biskit.Reduce( autocap=True ). The capping is performed by 
      L{Biskit.PDBCleaner} called from within Reduce.
    
    * mapping Amber partial atomic charges into the structure with 
      Biskit.AtomCharger()
      
    * setting up the various delphi runs with L{Biskit.Delphi}.
    
    A point worth looking at is the automatic capping of protein chain
    breaks and premature terminals with ACE and NME residues. This should
    generally be a good idea but the automatic discovery of premature C-termini
    or N-termini is guess work at best. See L{Biskit.PDBCleaner} for a more
    detailed discussion. You can override the default behaviour by setting
    autocap=False (no capping at all, this is now the default) and you can 
    then provide a complex structure that is already pre-treated by 
    L{Biskit.Reduce}.
    For example:
    
    >>> m = PDBModel('mycomplex.pdb')
    >>> m = Reduce( m, capN=[0], capC=[2] )
    >>> com = Biskit.Complex( m.takeChains([0]), m.takeChains([1,2]) )
    >>> dg = DelphiBindingEnergy( com, protonate=False )
    
    In this case, the original structure would receive a ACE N-terminal
    capping of the first chain and the second chain would receive a C-terminal
    NME capping residue. The first chain is considered receptor and the second
    and third chain are considered ligand.
    
    @see: L{Biskit.PDBCleaner}
    @see: L{Biskit.Reduce}
    @see: L{Biskit.AtomCharger}
    @see: L{Biskit.Delphi}
    """
    def __init__(self,
                 com,
                 protonate=True,
                 addcharge=True,
                 indi=4.0,
                 exdi=80.0,
                 salt=0.15,
                 ionrad=2,
                 prbrad=1.4,
                 bndcon=4,
                 scale=2.3,
                 perfil=60,
                 template=None,
                 topologies=None,
                 f_charges=None,
                 verbose=True,
                 debug=False,
                 log=None,
                 tempdir=None,
                 cwd=None,
                 **kw):
        """
        @param com: complex to analyze
        @type  com: Biskit.Complex
        @param protonate: (re-)build hydrogen atoms with reduce program (True)
                          see L{Biskit.Reduce}
        @type  protonate: bool
        @param addcharge: Assign partial charges from Amber topologies (True)
        @type  addcharge: bool
        @param indi: interior dilectric (4.0)
        @param exdi: exterior dielectric (80.0)
        @param salt: salt conc. in M (0.15)
        @param ionrad: ion radius (2)
        @param prbrad: probe radius (1.4) 
        @param bndcon: boundary condition (4, delphi default is 2)
        @param scale:  grid spacing (2.3)
        @param perfil: grid fill factor in % (for automatic grid, 60) 
        
        @param template: delphi command file template [None=use default]
        @type  template: str
        @param f_radii: alternative delphi atom radii file [None=use default]
        @type  f_radii: str
        @param topologies: alternative list of residue charge/topology files
                           [default: amber/residues/all*]
        @type  topologies: [ str ]
        @param f_charges: alternative delphi charge file 
                          [default: create custom]
        @type  f_charges: str

        @param kw: additional key=value parameters for Delphi or Executor:
        @type  kw: key=value pairs
        ::
          debug    - 0|1, keep all temporary files (default: 0)
          verbose  - 0|1, print progress messages to log (log != STDOUT)
          node     - str, host for calculation (None->local) NOT TESTED
                          (default: None)
          nice     - int, nice level (default: 0)
          log      - Biskit.LogFile, program log (None->STOUT) (default: None)
        """
        self.com = com
        self.delphicom = None

        self.protonate = protonate
        self.addcharge = addcharge
        ## DELPHI run parameters
        self.indi = indi  # interior dilectric(4.0)
        self.exdi = exdi  # exterior dielectric(80.0)
        self.salt = salt  # salt conc. in M (0.15)
        self.ionrad = ionrad  # ion radius (2)
        self.prbrad = prbrad  # probe radius (1.4)
        self.bndcon = bndcon  # boundary condition (4, delphi default is 2)

        ## DELPHI parameters for custom grid
        self.scale = scale  # grid spacing (1.2 / A)
        self.perfil = perfil  # grid fill factor in % (for automatic grid, 60)

        ## DELPHI parameter file and Amber residue definitions or charge file
        self.template = template
        self.topologies = topologies
        self.f_charges = f_charges

        ## pump everything else into name space, too
        self.__dict__.update(kw)

        ## prepare globally valid grid
        self.grid = None

        self.verbose = verbose
        self.log = log or StdLog()
        self.debug = debug
        self.tempdir = tempdir
        self.cwd = cwd

        self.ezero = self.esalt = None  # raw results assigned by run()

    def prepare(self):
        """
        """
        mrec = self.com.rec()
        mlig = self.com.lig()

        m = mrec.concat(mlig)

        if self.protonate:
            if self.verbose:
                self.log.add('\nRe-building hydrogen atoms...')

            tempdir = self.tempdir
            if tempdir:
                tempdir += '/0_reduce'

            r = Reduce(m,
                       tempdir=tempdir,
                       log=self.log,
                       autocap=False,
                       debug=self.debug,
                       verbose=self.verbose)
            m = r.run()

        if self.addcharge:
            if self.verbose:
                self.log.add('\nAssigning charges from Amber topologies...')

            ac = AtomCharger(log=self.log, verbose=self.verbose)
            ac.charge(m)

        mrec = m.takeChains(range(mrec.lenChains()))
        mlig = m.takeChains(range(mrec.lenChains(), m.lenChains()))

        self.delphicom = Complex(mrec, mlig)

    def setupDelphi(self, model, grid={}, **kw):
        """
        """
        params = copy.copy(self.__dict__)
        params.update(kw)
        params['protonate'] = False  # run reduce once for complex only
        params['addcharge'] = False  # run AtomCharger once for complex only

        d = Delphi(model, **params)
        d.setGrid(**grid)

        return d

    def processThreesome(self, **kw):
        """
        Calculate Delphi energies for rec, lig and complex
        @return: result dictionaries for com, rec, lig
        @rtype: ( dict, dict, dict )
        """
        dcom = self.setupDelphi(self.delphicom.model(), **kw)
        grid = dcom.getGrid()

        drec = self.setupDelphi(self.delphicom.rec(), grid=grid, **kw)
        dlig = self.setupDelphi(self.delphicom.lig(), grid=grid, **kw)

        if self.verbose:
            self.log.add('\nrunning Delphi for complex...')
        r_com = dcom.run()

        if self.verbose:
            self.log.add('\nrunning Delphi for receptor...')
        r_rec = drec.run()

        if self.verbose:
            self.log.add('\nrunning Delphi for ligand...')
        r_lig = dlig.run()

        return r_com, r_rec, r_lig

    def processSixsome(self, **kw):
        """
        Calculate Delphi energies for rec, lig and complex with and without
        salt.
        @return: com, rec, lig delphi calculations with and without ions
        @rtype: ( {}, {} )
        """
        ## with ions
        if self.verbose:
            self.log.add('\nDelphi calculations with %1.3f M salt' % self.salt)
            self.log.add('=======================================')
        ri_com, ri_rec, ri_lig = self.processThreesome(salt=self.salt, **kw)

        ## w/o ions
        if self.verbose:
            self.log.add('\nDelphi calculations with zero ionic strenght')
            self.log.add('==============================================')
        r_com, r_rec, r_lig = self.processThreesome(salt=0.0, **kw)

        rsalt = {'com': ri_com, 'rec': ri_rec, 'lig': ri_lig}
        rzero = {'com': r_com, 'rec': r_rec, 'lig': r_lig}

        if self.verbose:
            self.log.add('\nRaw results')
            self.log.add('============')
            self.log.add('zero ionic strength: \n' + str(rzero))
            self.log.add('\nwith salt: \n' + str(rsalt))

        return rzero, rsalt

    def bindingEnergy(self, ezero, esalt):
        """
        Calculate electrostatic component of the free energy of binding 
        according to energy partitioning formula.
        @param ezero: delphi energies calculated at zero ionic strength
        @type  ezero: {'com':{}, 'rec':{}, 'lig':{} }
        @param esalt: delphi energies calculated with ions (see salt=)
        @type  esalt: {'com':{}, 'rec':{}, 'lig':{} }
        @return: {'dG_kt': free E. in units of kT, 
                  'dG_kcal': free E. in units of kcal / mol, 
                  'dG_kJ': free E. in units of kJ / mol,
                  'coul': coulomb contribution in kT , 
                  'solv': solvation contribution in kT, 
                  'ions': salt or ionic contribution in kT }
        @rtype: dict { str : float }
        """
        delta_0 = {}
        delta_i = {}
        for k, v in ezero['com'].items():
            delta_0[k] = ezero['com'][k] - ezero['rec'][k] - ezero['lig'][k]
            delta_i[k] = esalt['com'][k] - esalt['rec'][k] - esalt['lig'][k]

        dG_coul = delta_0['ecoul']
        ddG_rxn = delta_0['erxn']
        ddG_ions = delta_i['egrid'] - delta_0['egrid']

        dG = dG_coul + ddG_rxn + ddG_ions  # in units of kT
        dG_kcal = round(dG * 0.593, 3)  # kcal / mol
        dG_kJ = round(dG * 2.479, 3)  # kJ / mol

        r = {
            'dG_kt': dG,
            'dG_kcal': dG_kcal,
            'dG_kJ': dG_kJ,
            'coul': dG_coul,
            'solv': ddG_rxn,
            'ions': ddG_ions
        }
        return r

    def run(self):
        """
        Prepare structures and perform Delphi calculations for complex, 
        receptor and ligand, each with and without ions (salt). Calculate
        free energy of binding according to energy partitioning.
        Detailed delphi results are saved into object fields 'ezero' and
        'esalt' for calculations without and with ions, respectively.
        
        @return: {'dG_kt': free E. in units of kT, 
                  'dG_kcal': free E. in units of kcal / mol, 
                  'dG_kJ': free E. in units of kJ / mol,
                  'coul': coulomb contribution in kT , 
                  'solv': solvation contribution in kT, 
                  'ions': salt or ionic contribution in kT }
        @rtype: dict { str : float }
        """
        self.prepare()
        self.ezero, self.esalt = self.processSixsome()
        self.result = self.bindingEnergy(self.ezero, self.esalt)

        self.delphicom.info['dG_delphi'] = self.result
        self.com.info['dG_delphi'] = self.result

        for com in [self.delphicom, self.com]:
            key = 'delphi_%4.2fsalt' % self.salt
            com.rec_model.info[key] = self.esalt['rec']
            com.lig_model.info[key] = self.esalt['lig']
            com.lig_model.lig_transformed = None
            key = 'delphi_0salt'
            com.rec_model.info[key] = self.ezero['rec']
            com.lig_model.info[key] = self.ezero['lig']
            com.lig_model.lig_transformed = None  # reset Complex.lig() cache
            com.info[key] = self.ezero['com']

        return self.result
Exemplo n.º 15
0
    def prepareTraj(self, fname, ref=None, cast=1):
        """
        Prepare trajectory for Amber.

        :param fname: path to EnsembleTraj OR ( EnsembleTraj, EnsembleTraj )
        :type  fname: str OR (str,str)
        :param ref: reference structure
        :type  ref: EnsembleTraj
        :param cast: cast to reference (same atom content) (default: 1)
        :type  cast: 1|0

        :return: split, fitted or shuffled, etc. trajectory instance
        :rtype: EnsembleTraj OR (EnsembleTraj, EnsembleTraj )
        """
        ## Load 1 or 2
        if self.__splitFilenames(fname):
            f1, f2 = self.__splitFilenames(fname)
            t = self.loadTraj(f1), self.loadTraj(f2)
        else:
            t = self.loadTraj(fname)
            if self.chains:
                t = t.takeChains(self.chains)

        ## split 1 into 2 if necessary
        if not type(t) is tuple and self.border:
            lig = range(self.border, t.ref.lenChains())
            t = t.takeChains(range(self.border)), t.takeChains(lig)

        ## check 2 trajectories were suplied
        if not type(t) is tuple and (self.shift or self.shuffle or self.split):
            raise EntropistError('split,shift,shuffle require -border.')

        ## adapt reference to type of trajectory input
        if ref and type(t) is tuple and not isinstance(ref, Complex):
            rec = ref.takeChains(range(t[0].ref.lenChains()))
            lig = ref.takeChains(
                range(t[0].ref.lenChains(), t[1].ref.lenChains()))
            ref = Complex(rec, lig)

        if ref and type(t) is not tuple and isinstance(ref, Complex):
            ref = ref.rec_model.concat(ref.lig())

        ## remove member trajectories (if requested)
        t = self.__removeMembers(t)

        ## cast 1 or 2
        if cast and type(t) is not tuple:
            self.castTraj(t, ref)

        if cast and type(t) is tuple:
            self.castTraj(t[0], ref.rec_model)
            self.castTraj(t[1], ref.lig_model)

        ## reorder one half (requires -border or file name pair )
        if self.shift:
            t = self.shiftTraj(t[0], self.shift), t[1]

        if self.shuffle:
            t = self.shuffleTraj(t[0]), self.shuffleTraj(t[1])

        ## fit seperately (requires -border or file name pair)
        if self.split and ref:
            self.fit(t[0], ref.rec())
            self.fit(t[1], ref.lig())
        if self.split and not ref:
            self.fit(t[0])
            self.fit(t[1])

        if type(t) is tuple:
            t = t[0].concatAtoms(t[1])
            ref = ref.rec_model.concat(ref.lig())

        ## joint fit
        if not self.split:
            self.fit(t, ref)

        if self.verbose:
            self.log.add( 'Analysing trajectory with %i atoms and %i frames.' \
                          % (t.lenAtoms(), t.lenFrames()))

        return t