Skip to content
Snippets Groups Projects
Select Git revision
  • 81ff0ff9f8ee203fb6cf74e6fe952704fd9c825c
  • master default protected
  • develop protected
  • cmake_boost_refactor
  • ubuntu_ci
  • mmtf
  • non-orthogonal-maps
  • no_boost_filesystem
  • data_viewer
  • 2.11.1
  • 2.11.0
  • 2.10.0
  • 2.9.3
  • 2.9.2
  • 2.9.1
  • 2.9.0
  • 2.8.0
  • 2.7.0
  • 2.6.1
  • 2.6.0
  • 2.6.0-rc4
  • 2.6.0-rc3
  • 2.6.0-rc2
  • 2.6.0-rc
  • 2.5.0
  • 2.5.0-rc2
  • 2.5.0-rc
  • 2.4.0
  • 2.4.0-rc2
29 results

superpose.py

Blame
  • superpose.py 11.83 KiB
    """
    Superposition of structures made simple.
    
    Authors: Stefan Bienert
    """
    
    import math
    import ost.mol.alg
    import ost.seq.alg
    
    def ParseAtomNames(atoms):
      """
      Parses different representations of a list of atom names and returns a
      :class:`set`, understandable by :func:`~ost.mol.alg.MatchResidueByNum`. In
      essence, this function translates
    
      * None to ``None``
    
      * 'all' to ``None``
    
      * 'backbone' to ``set(['N', 'CA', 'C', 'O'])``
    
      * 'aname1, aname2' to ``set(['aname1', 'aname2'])``
    
      * ``['aname1', 'aname2']``  to ``set(['aname1', 'aname2'])``
    
      :param atoms: Identifier or list of atoms
      :type atoms: :class:`str`, :class:`list`, :class:`set`
      :returns: A :class:`set` of atoms.
      """
      ## get a set of atoms or None
      if atoms==None:
        return None
      if isinstance(atoms, str):
        if atoms.upper()=='ALL':
          return None
        if atoms.upper()=='BACKBONE':
          return set(['N', 'CA', 'C', 'O'])
        ## if no recognised expression, split at ','
        return set([a.strip() for a in atoms.split(',')])
      return set(atoms)
    
    
    def _EmptyView(view):
      """
      for internal use, only
      """
      if isinstance(view, ost.mol.EntityHandle):
        return view.CreateEmptyView()
      return view.handle.CreateEmptyView()
    
    
    def _fetch_atoms(r_a, r_b, result_a, result_b, atmset):
      """
      for internal use, only
      """
      ## compare atoms of residues
      for a_a in r_a.GetAtomList():
        if atmset==None or a_a.name in atmset:
          a_b = r_b.FindAtom(a_a.name)
          if a_b.IsValid():
            result_a.AddAtom(a_a)
            result_b.AddAtom(a_b)
      return result_a, result_b
    
    
    def _no_of_chains(ent_a, ent_b):
      """
      for internal use, only
      """
      ## get lower no. of chains
      if ent_a.chain_count < ent_b.chain_count:
        return ent_a.chain_count
      return ent_b.chain_count
    
    
    def MatchResidueByNum(ent_a, ent_b, atoms='all'):
      """
      Returns a tuple of views containing exactly the same number of atoms.
      Residues are matched by residue number. A subset of atoms to be included in
      the views can be specified in the **atoms** argument. Regardless of what the
      list of **atoms** says, only those present in two matched residues will be
      included in the views. Chains are processed in the order they occur in the
      entities. If **ent_a** and **ent_b** contain a different number of chains,
      processing stops with the lower count.
    
      :param ent_a: The first entity
      :type ent_a: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param ent_b: The second entity
      :type ent_b: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param atoms: The subset of atoms to be included in the two views.
      :type atoms: :class:`str`, :class:`list`, :class:`set`
      :returns: Two :class:`~ost.mol.EntityView` instances with the same amount of
                residues matched by number. Each residue will have the same number
                & type of atoms.
      """
      ## init. final views
      result_a=_EmptyView(ent_a)
      result_b=_EmptyView(ent_b)
      n_chains=_no_of_chains(ent_a, ent_b)
      atmset=ParseAtomNames(atoms)
      ## iterate chains
      for i in range(0, n_chains):
        chain_a=ent_a.chains[i]
        chain_b=ent_b.chains[i]
        residues_a=iter(chain_a.residues)
        ## decide on order of residues
        if chain_a.InSequence() and chain_b.InSequence():
          residues_b=iter(chain_b.residues)
          ## check residues & copy to views
          try:
            while True:
              r_a=residues_a.next()
              r_b=residues_b.next()
              while r_a.number<r_b.number:
                r_a=residues_a.next()
              while r_b.number<r_a.number:
                r_b=residues_b.next()
              assert r_a.number==r_b.number
              result_a,result_b=_fetch_atoms(r_a, r_b, result_a, result_b, atmset)
          except StopIteration:
            pass
        else:
          ## iterate one list of residues, search in other list
          try:
            while True:
              r_a=residues_a.next()
              r_b=chain_b.FindResidue(r_a.number)
              if r_b.IsValid():
                result_a,result_b=_fetch_atoms(r_a, r_b, result_a, result_b, atmset)
          except StopIteration:
            pass
      result_a.AddAllInclusiveBonds()
      result_b.AddAllInclusiveBonds()
      return result_a, result_b
    
    
    def MatchResidueByIdx(ent_a, ent_b, atoms='all'):
      """
      Returns a tuple of views containing exactly the same number of atoms.
      Residues are matched by position in the chains of an entity. A subset of
      atoms to be included in the views can be specified in the **atoms** argument.
      Regardless of what the list of **atoms** says, only those present in two
      matched residues will be included in the views. Chains are processed in order
      of appearance. If **ent_a** and **ent_b** contain a different number of
      chains, processing stops with the lower count. The number of residues per
      chain is supposed to be the same.
    
      :param ent_a: The first entity
      :type ent_a: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param ent_b: The second entity
      :type ent_b: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param atoms: The subset of atoms to be included in the two views.
      :type atoms: :class:`str`, :class:`list`, :class:`set`
      :returns: Two :class:`~ost.mol.EntityView` instances with the same amount of
                residues matched by position. Each residue will have the same number
                & type of atoms.
      """
      not_supported="MatchResidueByIdx has no support for chains of different "\
                   +"lengths"
      ## init. final views
      result_a=_EmptyView(ent_a)
      result_b=_EmptyView(ent_b)
      n_chains=_no_of_chains(ent_a, ent_b)
      atmset=ParseAtomNames(atoms)
      ## iterate chains
      for i in range(0, n_chains):
        chain_a=ent_a.chains[i]
        chain_b=ent_b.chains[i]
        ## check equal no. of residues
        if chain_a.residue_count!=chain_b.residue_count:
          raise RuntimeError(not_supported)
        ## iterate residues & copy to views
        for r_a, r_b in zip(chain_a.residues, chain_b.residues):
          result_a,result_b=_fetch_atoms(r_a, r_b, result_a, result_b, atmset)
      result_a.AddAllInclusiveBonds()
      result_b.AddAllInclusiveBonds()
      return result_a, result_b
    
    
    def _MatchResidueByAln(ent_a, ent_b, atoms, alnmethod):
      """
      For internal use, only
      """
      ## init. final views
      result_a = _EmptyView(ent_a)
      result_b = _EmptyView(ent_b)
      n_chains = _no_of_chains(ent_a, ent_b)
      atmset = ParseAtomNames(atoms)
      ## iterate chains
      for i in range(0, n_chains):
        chain_a = ent_a.chains[i]
        chain_b = ent_b.chains[i]
        ## fetch residues
        s_a = ''.join([r.one_letter_code
                       for r in chain_a.Select('protein=True').residues])
        s_b = ''.join([r.one_letter_code
                       for r in chain_b.Select('protein=True').residues])
        ## create sequence from residue lists & alignment
        seq_a = ost.seq.CreateSequence(chain_a.name, s_a)
        seq_b = ost.seq.CreateSequence(chain_b.name, s_b)
        aln_a_b = alnmethod(seq_a, seq_b, ost.seq.alg.BLOSUM62)
        ## evaluate alignment
        for aln in aln_a_b:
          ## bind chain to alignment
          aln.AttachView(0, chain_a.Select('protein=True'))
          aln.AttachView(1, chain_b.Select('protein=True'))
          ## select residues (only replacement edges)
          for i in range(0, aln.GetLength()):
            if aln.sequences[0][i]!='-' and aln.sequences[1][i]!='-':
              r_a = aln.GetResidue(0,i)
              r_b = aln.GetResidue(1,i)
              result_a,result_b=_fetch_atoms(r_a, r_b, result_a, result_b, atmset)
      result_a.AddAllInclusiveBonds()
      result_b.AddAllInclusiveBonds()
      return result_a, result_b
    
    def MatchResidueByLocalAln(ent_a, ent_b, atoms='all'):
      """
      Match residues by local alignment. Takes **ent_a** and **ent_b**, extracts
      the sequences chain-wise and aligns them in Smith/Waterman manner using the
      BLOSUM62 matrix for scoring. The residues of the entities are then matched
      based on this alignment. Only atoms present in both residues are included in
      the views. Chains are processed in order of appearance. If **ent_a** and
      **ent_b** contain a different number of chains, processing stops with
      the lower count.
    
      :param ent_a: The first entity
      :type ent_a: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param ent_b: The second entity
      :type ent_b: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param atoms: The subset of atoms to be included in the two views.
      :type atoms: :class:`str`, :class:`list`, :class:`set`
      :returns: Two :class:`~ost.mol.EntityView` instances with the same number of
                residues. Each residue will have the same number & type of atoms.
      """
      return _MatchResidueByAln(ent_a, ent_b, atoms, ost.seq.alg.LocalAlign)
    
    def MatchResidueByGlobalAln(ent_a, ent_b, atoms='all'):
      """
      Match residues by global alignment. Takes **ent_a** and **ent_b**, extracts
      the sequences chain-wise and aligns them in Needleman/Wunsch manner using the
      BLOSUM62 matrix for scoring. The residues of the entities are then matched
      based on this alignment. Only atoms present in both residues are included in
      the views. Chains are processed in order of appearance. If **ent_a** and
      **ent_b** contain a different number of chains, processing stops with
      the lower count.
    
      :param ent_a: The first entity
      :type ent_a: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param ent_b: The second entity
      :type ent_b: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param atoms: The subset of atoms to be included in the two views.
      :type atoms: :class:`str`, :class:`list`, :class:`set`
      :returns: Two :class:`~ost.mol.EntityView` instances with the same number of
                residues. Each residue will have the same number & type of atoms.
      """
      return _MatchResidueByAln(ent_a, ent_b, atoms, ost.seq.alg.GlobalAlign)
    
    
    def Superpose(ent_a, ent_b, match='number', atoms='all'):
      """
      Superposes the model entity onto the reference. To do so, two views are
      created, returned with the result. **atoms** describes what goes in to these
      views and **match** the selection method. For superposition,
      :func:`~ost.mol.alg.SuperposeSVD` is called. For matching, following methods
      are recognised:
    
      * ``number`` - select residues by residue number, includes **atoms**, calls
        :func:`~ost.mol.alg.MatchResidueByNum`
    
      * ``index`` - select residues by index in chain, includes **atoms**, calls
        :func:`~ost.mol.alg.MatchResidueByIdx`
    
      * ``local-aln`` - select residues from a Smith/Waterman alignment, includes
        **atoms**, calls :func:`~ost.mol.alg.MatchResidueByLocalAln`
    
      * ``global-aln`` - select residues from a Needleman/Wunsch alignment, includes
        **atoms**, calls :func:`~ost.mol.alg.MatchResidueByGlobalAln`
    
      :param ent_a: The model entity
      :type ent_a: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param ent_b: The reference entity
      :type ent_b: :class:`~ost.mol.EntityView` or :class:`~ost.mol.EntityHandle`
      :param match: Method to gather residues/ atoms
      :type match: :class:`str`
      :param atoms: The subset of atoms to be used in the superposition.
      :type atoms: :class:`str`, :class:`list`, :class:`set`
      :returns: An instance of :class:`SuperpositionResult`, containing members
    
                * ``rmsd`` - RMSD of the superposed entities
    
                * ``view1`` - First :class:`~ost.mol.EntityView` used
    
                * ``view2`` - Second :class:`~ost.mol.EntityView` used
      """
      not_supported="Superpose called with unsupported matching request."
      ## create views to superpose
      if match.upper() == 'NUMBER':
        view_a, view_b = MatchResidueByNum(ent_a, ent_b, atoms)
      elif match.upper() == 'INDEX':
        view_a, view_b=MatchResidueByIdx(ent_a, ent_b, atoms)
      elif match.upper() == 'LOCAL-ALN':
        view_a, view_b=_MatchResidueByAln(ent_a, ent_b, atoms,
                                          ost.seq.alg.LocalAlign)
      elif match.upper() == 'GLOBAL-ALN':
        view_a, view_b=_MatchResidueByAln(ent_a, ent_b, atoms,
                                          ost.seq.alg.GlobalAlign)
      else:
        raise ValueError(not_supported)
      ## action
      res=ost.mol.alg.SuperposeSVD(view_a, view_b)
      return res