"""
Find salt-bridge interactions.
Examples
========
Find all salt bridge interactions within a protein::
st = structure.Structure.read("protein.mae.gz")
for atom1, atom2 in get_salt_bridges(st):
print(f"Salt bridge between atoms {atom1.index} and {atom2.index}")
Find all salt bridges within a single protein chain::
st = structure.Structure.read("protein.mae.gz")
atoms = st.chain["C"].getAtomIndices()
for atom1, atom2 in get_salt_bridges(st, atoms):
print(f"Salt bridge between atoms {atom1.index} and {atom2.index}")
Find all salt bridges between a protein and a ligand::
with StructureReader("protein_and_ligand.mae.gz") as reader:
prot, lib = reader
for atom1, atom2 in get_salt_bridges(prot, struc2=lig):
print(f"Salt bridge between atoms {atom1.index} and {atom2.index}")
"""
# Copyright Schrodinger, LLC. All rights reserved.
import enum
from schrodinger import structure
from schrodinger.infra import mmbitset
from schrodinger.infra import structure as infrastructure
from schrodinger.structutils import pbc_tools
DEFAULT_MAX_DIST = 4.0
OrderBy = enum.Enum("OrderBy", ["AnionCation", "InputOrder"])
[docs]def get_salt_bridges(struc1,
group1=None,
struc2=None,
group2=None,
cutoff=5,
order_by=OrderBy.AnionCation,
honor_pbc=True):
"""
Calculate all salt bridges within or between the specified atoms. If struc2
or group2 are given, then this function will return salt bridges between the
two structures/groups of atoms. If neither struc2 nor group2 are given,
then this function will return salt bridges within a single structure/group
of atoms.
:param struc1: The structure to analyze
:type struc1: `schrodinger.structure.Structure`
:param group1: The list of atom indices in `struc1` to analyze. If not
given, all atoms in struc1 will be analyzed.
:type group1: list
:param struc2: The second structure to analyze. If `group2` is given
but `struc2` is not, then `struc1` will be used.
:type struc2: `schrodinger.structure.Structure`
:param group2: The list of atom indices in `struc2` to analyze. If
`struc2` is given but `group2` is not, then all atoms in `struc2` will be
analyzed.
:type group2: list
:param cutoff: The maximum distance allowed for salt bridges
:type cutoff: float
:param order_by: How the returned salt bridge atom should be ordered. If
`OrderBy.AnionCation`, then each salt bridge will be returned as a tuple of
(anion atom, cation atom). If `OrderBy.InputOrder`, then each salt bridge
will be returned as a tuple of (atom from struc1/group1, atom from
struc2/group2).
:type order_by: `OrderBy`
:return: A list of salt bridges, where each salt bridge is represented by a
tuple of two `schrodinger.structure._StructureAtom` objects.
:rtype: list
"""
if struc2 is None and group2 is None and order_by is OrderBy.InputOrder:
err = ("Cannot order by input order when finding salt bridges within a "
"single structure or region.")
raise ValueError(err)
salt_bridges, bs1 = _get_wrapped_sb_list(struc1, group1, struc2, group2,
cutoff, honor_pbc)
salt_bridges = list(map(_convert_salt_bridge, salt_bridges))
if order_by is OrderBy.InputOrder:
salt_bridges = [_to_input_order(sb, struc1, bs1) for sb in salt_bridges]
return salt_bridges
def _get_wrapped_sb_list(struc1,
group1,
struc2,
group2,
cutoff,
honor_pbc=True):
"""
Get the salt bridge list returned by
`schrodinger.infra.structure.get_salt_bridges`.
:param struc1: The structure to analyze
:type struc1: `schrodinger.structure.Structure`
:param group1: The list of atom indices in `struc1` to analyze. If not
given, all atoms in struc1 will be analyzed.
:type group1: list
:param struc2: The second structure to analyze. If `group2` is given
but `struc2` is not, then `struc1` will be used.
:type struc2: `schrodinger.structure.Structure`
:param group2: The list of atom indices in `struc2` to analyze. If
`struc2` is given but `group2` is not, then all atoms in `struc2` will be
analyzed.
:type group2: list
:param cutoff: The maximum distance allowed for salt bridges
:type cutoff: float
:return: A list of salt bridges, where each salt bridge is represented by a
tuple of two `schrodinger.infra.structure.StructureAtom` objects in (anion,
cation) order.
:rtype: list
"""
params = get_salt_bridge_params(cutoff=cutoff)
bs1 = _convert_group(group1, struc1)
pbc = pbc_tools.get_pbc(struc1, struc2, honor_pbc)
if struc2 is None and group2 is None:
salt_bridges = infrastructure.get_salt_bridges(struc1, bs1, params, pbc)
else:
if struc2 is None:
struc2 = struc1
bs2 = _convert_group(group2, struc2)
salt_bridges = infrastructure.get_salt_bridges(struc1, bs1, struc2, bs2,
params, pbc)
return salt_bridges, bs1
def _convert_group(group, struc):
"""
Convert a list of atom indices to a bitset.
:param group: A list of atom indices. If None, the bitset will contain all
atoms in `struc`.
:type group: list
:param struc: The structure that the `group` atom indices refer to
:type struc: `schrodinger.structure.Structure`
:return: The bitset
:rtype: `mmbitset.Bitset`
"""
bs = mmbitset.Bitset(size=struc.atom_total)
if group is not None:
list(map(bs.set, group))
else:
bs.fill()
return bs
def _convert_salt_bridge(salt_bridge):
"""
Convert `schrodinger.infra.structure.SaltBridge` object to a tuple of
(anion, cation) `schrodinger.structure._StructureAtom` objects.
"""
anion = _convert_atom(salt_bridge.getAnion())
cation = _convert_atom(salt_bridge.getCation())
return (anion, cation)
def _convert_atom(infra_atom):
"""
Convert a `schrodinger.infra.structure.StructureAtom` object into a
`schrodinger.structure._StructureAtom`
"""
atom_index = infra_atom.getIndex()
cpp_st = infra_atom.getStructure()
st = structure.Structure(cpp_st)
return st.atom[atom_index]
def _to_input_order(salt_bridge, struc1, bs1):
"""
Switch the salt bridge tuple from (anion, cation) order to input order.
:param salt_bridge: The salt bridge to re-order, as a tuple of
`schrodinger.structure._StructureAtom` objects.
:type salt_bridge: tuple
:param struc1: The first structure
:type struc1: `schrodinger.structure.Structure`
:param bs1: The bitset of group 1 atoms
:type bs1: `mmbitset.Bitset`
:return: The re-ordered salt bridge, as a tuple of
`schrodinger.structure._StructureAtom` objects.
:rtype: tuple
"""
anion, cation = salt_bridge
if anion._ct == struc1 and bs1.get(anion.index):
return anion, cation
else:
return cation, anion
[docs]def get_salt_bridge_params(
cutoff: float = None) -> infrastructure.SaltBridgeParams:
"""
Return salt bridge `SaltBridgeParams` object with the given criteria.
:param cutoff: See `get_salt_bridges`.
:return: `SaltBridgeParams` with the given salt bridge criteria.
"""
params = infrastructure.SaltBridgeParams()
params.setCutoff(cutoff)
return params