Source code for schrodinger.analysis.substructure
"""
Functionality for generating connected molecule substructures.
Copyright Schrodinger, LLC. All rights reserved.
"""
[docs]class ConnectedMoleculeChain:
    """
    Superpose st2 and st1 by moving each molecule independently.
    This is accomplished by fixing the first molecule of st1 (with atoms in the atom
    list), and aligning all molecules in st2 that contain atoms which map to the atoms
    in this molecule of st1. These atoms are now fixed and all molecules in st1 which
    contain atoms which map to the now-fixed atoms of st2 are aligned to the existing
    fixed st1/st2 molecules. This fixing and aligning is continued until all molecules
    in the ensemble connected to the first molecule in st1 is completed. If this did not
    include all molecules in the st1/st2 system, the next, non-fixed st1 molecule is
    used to start a new fixing/aligning chain. If there are still un-fixed molecules
    when all the st1 molecules are fixed, then these atoms only appear in st2 and so
    are fixed at the input st2 locations.
    :param st1: first structure
    :type st1: Structure
    :param atlist1: list of atom indexes for structure 1
    :type atlist1: list of ints
    :param st2: second structure
    :type st2: Structure
    :param atlist2: list of atom indexes for structure 2
    :type atlist2: list of ints
    :return: rmsd, st2 and st1 rotated and translated to align
    """
[docs]    def __init__(self, st1, atlist1, st2, atlist2):
        self._st1 = st1
        self._st2 = st2
        self._atlist1 = atlist1
        self._atlist2 = atlist2
        self._atmap12 = dict(zip(atlist1, atlist2))
        self._atmap21 = dict(zip(atlist2, atlist1))
        self._n_mols_st1 = len(st1.molecule)
        self._n_mols = self._n_mols_st1 + len(st2.molecule) 
    def _initializeChain(self):
        """
        Begin the molecule connectivity chain.
        Arbitrarily, we consider the molecule which contains the first
        atom in the atom list in the first structure to be the first
        fixed molecule and start the chain here.
        """
        start_mol = self._st1.atom[self._atlist1[0]].molecule_number
        self._completed_mols = [start_mol]
        self._mol_offset = self._n_mols_st1
        self._atmap_move_to_fixed = self._atmap12
        self._st_fixed = self._st2
        self._st_move = self._st1
        self._st1_located_atoms = set(
            at for at in self._st1.molecule[start_mol].getAtomIndices()
            if at in self._atlist1)
        self._connected_mols = self._getNewConnections(start_mol)
        self._moving_st = 1
    def _setMovingParts(self):
        """
        Set the relevant parameters to propogate the molecule connectivity
        chain.
        This involves determining whether the next molecule in _connected_mols
        is in Structure1 or Structure2 and setting the corresponding atom map
        directions accordingly.
        """
        self._st_fixed = self._st2
        self._st_move = self._st1
        mol_idx = self._connected_mols.pop()
        self._completed_mols.append(mol_idx)
        self._atlist = self._atlist1
        self._atmap_move_to_fixed = self._atmap12
        self._mol_offset = self._n_mols_st1
        self._located_atoms = self._st1_located_atoms
        self._moving_st = 0
        if mol_idx > self._n_mols_st1:
            self._st_fixed = self._st1
            self._st_move = self._st2
            mol_idx -= self._n_mols_st1
            self._atlist = self._atlist2
            self._atmap_move_to_fixed = self._atmap21
            self._mol_offset = 0
            self._located_atoms = set(
                self._atmap12[idx] for idx in self._st1_located_atoms)
            self._moving_st = 1
        return mol_idx
    def _getNewConnections(self, mol_idx):
        """
        Get the molecules that have not already been moved in the fixed
        structure that are connected to the moved molecule
        A note about the variables used below:
        st_fixed: fixed structure that was not just moved
        st_move: structure that was just moved
        atmap_move_to_fixed: map from the moved structure to the fixed structure
        mol_offset: offset either 0 or # of molecules in structure 1, determined
        by which molecule is being moved
        mol_idx: index of just moved molecule (in the moved structure)
        This function returns a set of unfixed molecules in the chain
        connected to the just moved molecule.
        """
        at_in_fixed_st = [
            self._atmap_move_to_fixed[idx]
            for idx in self._st_move.molecule[mol_idx].getAtomIndices()
            if idx in self._atmap_move_to_fixed.keys()
        ]
        new_cnxns = set(self._st_fixed.atom[idx].molecule_number +
                        self._mol_offset for idx in at_in_fixed_st)
        new_cnxns = set(new_mols for new_mols in new_cnxns
                        if new_mols not in self._completed_mols)
        return new_cnxns
[docs]    def getConnectivityChain(self):
        """
        Build a chain of which structure moves, which atoms are fixed, and the
        corresponding moving atoms.
        :param return: The chain has the format (st_moving, fixed_list, moving_list) where
                       st_moving is 0 for the first structure and 1 for the second, and the
                       lists are lists of ints.
        :param type: 3-tuple of int, list of ints, list of ints
        """
        chain = []
        self._initializeChain()
        while len(self._completed_mols) != self._n_mols:
            while len(self._connected_mols) > 0:
                mol_idx = self._setMovingParts()
                move_list = [
                    idx
                    for idx in self._st_move.molecule[mol_idx].getAtomIndices()
                    if idx in self._atlist and idx in self._located_atoms
                ]
                if move_list:
                    fixed_list = [
                        self._atmap_move_to_fixed[i] for i in move_list
                    ]
                    chain.append((self._moving_st, fixed_list, move_list))
                    at_to_st1 = dict(zip(self._atlist, self._atlist1))
                    self._st1_located_atoms |= set(
                        at_to_st1[idx]
                        for idx in
                        self._st_move.molecule[mol_idx].getAtomIndices()
                        if idx in self._atlist)
                self._connected_mols |= self._getNewConnections(mol_idx)
            missing_ats = set(self._atlist1) - self._st1_located_atoms
            if len(missing_ats) > 0:
                new_start_mol = self._st1.atom[
                    missing_ats.pop()].molecule_number
                self._connected_mols = set([new_start_mol])
                self._st1_located_atoms |= set(
                    at for at in
                    self._st1.molecule[new_start_mol].getAtomIndices()
                    if at in self._atlist1)
            else:
                missing_mols = set(range(1, self._n_mols + 1)) - set(
                    self._completed_mols)
                if len(missing_mols) > 0:
                    self._connected_mols = set([missing_mols.pop()])
        return chain