Module protkit.properties.bond_angles

Implements class to calculate bond angles BondAngles to calculate bond angles in a residue, chain or protein.

Expand source code
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Authors:  Fred Senekal (FS)
# Contact:  fred@silicogenesis.com
# License:  GPLv3

"""
Implements class to calculate bond angles `BondAngles` to calculate bond angles
in a residue, chain or protein.
"""

from typing import List, Dict, Tuple

from protkit.structure.atom import Atom
from protkit.structure.residue import Residue
from protkit.structure.chain import Chain
from protkit.structure.protein import Protein

from protkit.geometry.math import Math

class BondAngles:
    HEAVY_ATOM_BOND_ANGLES = {
        "ALA": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB")
        },
        "ARG": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "NE"),
            ("CD", "NE", "CZ"),
            ("NE", "CZ", "NH1"),
            ("NE", "CZ", "NH2"),
            ("NH1", "CZ", "NH2")
        },
        "ASN": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "OD1"),
            ("CB", "CG", "ND2"),
            ("ND2", "CG", "OD1")
        },
        "ASP": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "OD1"),
            ("CB", "CG", "OD2"),
            ("OD1", "CG", "OD2")
        },
        "CYS": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "SG")
        },
        "GLU": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "OE1"),
            ("CG", "CD", "OE2"),
            ("OE1", "CD", "OE2")
        },
        "GLN": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "OE1"),
            ("CG", "CD", "NE2"),
            ("OE1", "CD", "NE2")
        },
        "GLY": {
            ("N", "CA", "C"),
            ("CA", "C", "O")
        },
        "HIS": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "ND1"),
            ("CB", "CG", "CD2"),
            ("CG", "ND1", "CE1"),
            ("ND1", "CE1", "NE2"),
            ("CE1", "NE2", "CD2"),
            ("NE2", "CD2", "CG"),
            ("CD2", "CG", "ND1")
        },
        "ILE": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG1"),
            ("CB", "CG1", "CD1"),
            ("CA", "CB", "CG2"),
            ("CG1", "CB", "CG2")
        },
        "LEU": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2")
        },
        "LYS": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "CE"),
            ("CD", "CE", "NZ")
        },
        "MET": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "SD"),
            ("CG", "SD", "CE")
        },
        "PHE": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2"),
            ("CG", "CD1", "CE1"),
            ("CD1", "CE1", "CZ"),
            ("CE1", "CZ", "CE2"),
            ("CZ", "CE2", "CD2"),
            ("CE2", "CD2", "CG")
        },
        "PRO": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "N"),
            ("CA", "N", "CD")
        },
        "SER": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "OG")
        },
        "THR": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "OG1"),
            ("CA", "CB", "CG2"),
            ("OG1", "CB", "CG2")
        },
        "TRP": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2"),
            ("CG", "CD1", "NE1"),
            ("CD1", "NE1", "CE2"),
            ("NE1", "CE2", "CD2"),
            ("CE2", "CD2", "CG"),
            ("CG", "CD2", "CE3"),
            ("NE1", "CE2", "CZ2"),
            ("CE3", "CD2", "CE2"),
            ("CD2", "CE2", "CZ2"),
            ("CE2", "CZ2", "CH2"),
            ("CZ2", "CH2", "CZ3"),
            ("CH2", "CZ3", "CE3"),
            ("CZ3", "CE3", "CD2")
        },
        "TYR": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2"),
            ("CG", "CD1", "CE1"),
            ("CG", "CD2", "CE2"),
            ("CD1", "CE1", "CZ"),
            ("CD2", "CE2", "CZ"),
            ("CE1", "CZ", "CE2"),
            ("CE1", "CZ", "OH"),
            ("CE2", "CZ", "OH")
        },
        "VAL": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG1"),
            ("CA", "CB", "CG2"),
            ("CG1", "CB", "CG2")
        }
    }

    @staticmethod
    def angle(a1: Atom, a2: Atom, a3: Atom) -> float:
        """
        Calculates the angle between three atoms.

        Args:
            a1 (Atom): The first atom.
            a2 (Atom): The second atom.
            a3 (Atom): The third atom.

        Returns:
            float: The angle between the three atoms.
        """
        return Math.angle(a1.x, a1.y, a1.z, a2.x, a2.y, a2.z, a3.x, a3.y, a3.z)

    @staticmethod
    def bond_angles_of_residue(residue: Residue,
                               assign_attribute: bool = False,
                               key: str = "bond_angles") -> Dict[Tuple[str, str, str], float]:
        """
        Returns the bond angles of a residue

        Args:
            residue (Residue): The residue for which to return the bond angles
            assign_attribute (bool): Whether to assign the bond angles to the residue
            key (str): The key to use to access the bond angles

        Returns:
            Dict[Tuple[str, str, str], float]: A dictionary of bond angles
        """
        angles = {}
        defined_angles = BondAngles.HEAVY_ATOM_BOND_ANGLES.get(residue.residue_type, set())

        for (atom1, atom2, atom3) in defined_angles:
            a1 = residue.get_atom(atom1)
            a2 = residue.get_atom(atom2)
            a3 = residue.get_atom(atom3)
            if a1 and a2 and a3:
                angles[(atom1, atom2, atom3)] = BondAngles.angle(a1, a2, a3)
            else:
                angles[(atom1, atom2, atom3)] = None

        if assign_attribute:
            residue.set_attribute(key, angles)

        return angles

    @staticmethod
    def bond_angles_of_chain(chain: Chain,
                             assign_attribute: bool = False,
                             key: str = "bond_angles") -> Dict[str, Dict]:
        """
        Returns the bond angles of a chain

        Args:
            chain (Chain): The chain for which to return the bond angles
            assign_attribute (bool): Whether to assign the bond angles to the chain
            key (str): The key to use to access the bond angles

        Returns:
            Dict[str, Dict]: A dictionary of bond angles.
        """
        angles = {}

        for residue in chain.residues:
            angles[residue.residue_code] = BondAngles.bond_angles_of_residue(residue, assign_attribute=assign_attribute, key=key)

        return angles

    @staticmethod
    def bond_angles_of_protein(protein: Protein,
                               assign_attribute: bool = False,
                               key: str = "bond_angles") -> Dict[str, Dict]:
        """
        Returns the bond angles of a protein

        Args:
            protein (Protein): The protein for which to return the bond angles
            assign_attribute (bool): Whether to assign the bond angles to the protein
            key (str): The key to use to access the bond angles

        Returns:
            Dict[str, Dict]: A dictionary of bond angles.
        """
        angles = {}

        for chain in protein.chains:
            angles[chain.chain_id] = BondAngles.bond_angles_of_chain(chain, assign_attribute=assign_attribute, key=key)

        return angles

Classes

class BondAngles
Expand source code
class BondAngles:
    HEAVY_ATOM_BOND_ANGLES = {
        "ALA": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB")
        },
        "ARG": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "NE"),
            ("CD", "NE", "CZ"),
            ("NE", "CZ", "NH1"),
            ("NE", "CZ", "NH2"),
            ("NH1", "CZ", "NH2")
        },
        "ASN": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "OD1"),
            ("CB", "CG", "ND2"),
            ("ND2", "CG", "OD1")
        },
        "ASP": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "OD1"),
            ("CB", "CG", "OD2"),
            ("OD1", "CG", "OD2")
        },
        "CYS": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "SG")
        },
        "GLU": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "OE1"),
            ("CG", "CD", "OE2"),
            ("OE1", "CD", "OE2")
        },
        "GLN": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "OE1"),
            ("CG", "CD", "NE2"),
            ("OE1", "CD", "NE2")
        },
        "GLY": {
            ("N", "CA", "C"),
            ("CA", "C", "O")
        },
        "HIS": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "ND1"),
            ("CB", "CG", "CD2"),
            ("CG", "ND1", "CE1"),
            ("ND1", "CE1", "NE2"),
            ("CE1", "NE2", "CD2"),
            ("NE2", "CD2", "CG"),
            ("CD2", "CG", "ND1")
        },
        "ILE": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG1"),
            ("CB", "CG1", "CD1"),
            ("CA", "CB", "CG2"),
            ("CG1", "CB", "CG2")
        },
        "LEU": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2")
        },
        "LYS": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "CE"),
            ("CD", "CE", "NZ")
        },
        "MET": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "SD"),
            ("CG", "SD", "CE")
        },
        "PHE": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2"),
            ("CG", "CD1", "CE1"),
            ("CD1", "CE1", "CZ"),
            ("CE1", "CZ", "CE2"),
            ("CZ", "CE2", "CD2"),
            ("CE2", "CD2", "CG")
        },
        "PRO": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD"),
            ("CG", "CD", "N"),
            ("CA", "N", "CD")
        },
        "SER": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "OG")
        },
        "THR": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "OG1"),
            ("CA", "CB", "CG2"),
            ("OG1", "CB", "CG2")
        },
        "TRP": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2"),
            ("CG", "CD1", "NE1"),
            ("CD1", "NE1", "CE2"),
            ("NE1", "CE2", "CD2"),
            ("CE2", "CD2", "CG"),
            ("CG", "CD2", "CE3"),
            ("NE1", "CE2", "CZ2"),
            ("CE3", "CD2", "CE2"),
            ("CD2", "CE2", "CZ2"),
            ("CE2", "CZ2", "CH2"),
            ("CZ2", "CH2", "CZ3"),
            ("CH2", "CZ3", "CE3"),
            ("CZ3", "CE3", "CD2")
        },
        "TYR": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG"),
            ("CB", "CG", "CD1"),
            ("CB", "CG", "CD2"),
            ("CD1", "CG", "CD2"),
            ("CG", "CD1", "CE1"),
            ("CG", "CD2", "CE2"),
            ("CD1", "CE1", "CZ"),
            ("CD2", "CE2", "CZ"),
            ("CE1", "CZ", "CE2"),
            ("CE1", "CZ", "OH"),
            ("CE2", "CZ", "OH")
        },
        "VAL": {
            ("N", "CA", "C"),
            ("CA", "C", "O"),
            ("N", "CA", "CB"),
            ("C", "CA", "CB"),
            ("CA", "CB", "CG1"),
            ("CA", "CB", "CG2"),
            ("CG1", "CB", "CG2")
        }
    }

    @staticmethod
    def angle(a1: Atom, a2: Atom, a3: Atom) -> float:
        """
        Calculates the angle between three atoms.

        Args:
            a1 (Atom): The first atom.
            a2 (Atom): The second atom.
            a3 (Atom): The third atom.

        Returns:
            float: The angle between the three atoms.
        """
        return Math.angle(a1.x, a1.y, a1.z, a2.x, a2.y, a2.z, a3.x, a3.y, a3.z)

    @staticmethod
    def bond_angles_of_residue(residue: Residue,
                               assign_attribute: bool = False,
                               key: str = "bond_angles") -> Dict[Tuple[str, str, str], float]:
        """
        Returns the bond angles of a residue

        Args:
            residue (Residue): The residue for which to return the bond angles
            assign_attribute (bool): Whether to assign the bond angles to the residue
            key (str): The key to use to access the bond angles

        Returns:
            Dict[Tuple[str, str, str], float]: A dictionary of bond angles
        """
        angles = {}
        defined_angles = BondAngles.HEAVY_ATOM_BOND_ANGLES.get(residue.residue_type, set())

        for (atom1, atom2, atom3) in defined_angles:
            a1 = residue.get_atom(atom1)
            a2 = residue.get_atom(atom2)
            a3 = residue.get_atom(atom3)
            if a1 and a2 and a3:
                angles[(atom1, atom2, atom3)] = BondAngles.angle(a1, a2, a3)
            else:
                angles[(atom1, atom2, atom3)] = None

        if assign_attribute:
            residue.set_attribute(key, angles)

        return angles

    @staticmethod
    def bond_angles_of_chain(chain: Chain,
                             assign_attribute: bool = False,
                             key: str = "bond_angles") -> Dict[str, Dict]:
        """
        Returns the bond angles of a chain

        Args:
            chain (Chain): The chain for which to return the bond angles
            assign_attribute (bool): Whether to assign the bond angles to the chain
            key (str): The key to use to access the bond angles

        Returns:
            Dict[str, Dict]: A dictionary of bond angles.
        """
        angles = {}

        for residue in chain.residues:
            angles[residue.residue_code] = BondAngles.bond_angles_of_residue(residue, assign_attribute=assign_attribute, key=key)

        return angles

    @staticmethod
    def bond_angles_of_protein(protein: Protein,
                               assign_attribute: bool = False,
                               key: str = "bond_angles") -> Dict[str, Dict]:
        """
        Returns the bond angles of a protein

        Args:
            protein (Protein): The protein for which to return the bond angles
            assign_attribute (bool): Whether to assign the bond angles to the protein
            key (str): The key to use to access the bond angles

        Returns:
            Dict[str, Dict]: A dictionary of bond angles.
        """
        angles = {}

        for chain in protein.chains:
            angles[chain.chain_id] = BondAngles.bond_angles_of_chain(chain, assign_attribute=assign_attribute, key=key)

        return angles

Class variables

var HEAVY_ATOM_BOND_ANGLES

Static methods

def angle(a1: Atom, a2: Atom, a3: Atom) ‑> float

Calculates the angle between three atoms.

Args

a1 : Atom
The first atom.
a2 : Atom
The second atom.
a3 : Atom
The third atom.

Returns

float
The angle between the three atoms.
Expand source code
@staticmethod
def angle(a1: Atom, a2: Atom, a3: Atom) -> float:
    """
    Calculates the angle between three atoms.

    Args:
        a1 (Atom): The first atom.
        a2 (Atom): The second atom.
        a3 (Atom): The third atom.

    Returns:
        float: The angle between the three atoms.
    """
    return Math.angle(a1.x, a1.y, a1.z, a2.x, a2.y, a2.z, a3.x, a3.y, a3.z)
def bond_angles_of_chain(chain: Chain, assign_attribute: bool = False, key: str = 'bond_angles') ‑> Dict[str, Dict]

Returns the bond angles of a chain

Args

chain : Chain
The chain for which to return the bond angles
assign_attribute : bool
Whether to assign the bond angles to the chain
key : str
The key to use to access the bond angles

Returns

Dict[str, Dict]
A dictionary of bond angles.
Expand source code
@staticmethod
def bond_angles_of_chain(chain: Chain,
                         assign_attribute: bool = False,
                         key: str = "bond_angles") -> Dict[str, Dict]:
    """
    Returns the bond angles of a chain

    Args:
        chain (Chain): The chain for which to return the bond angles
        assign_attribute (bool): Whether to assign the bond angles to the chain
        key (str): The key to use to access the bond angles

    Returns:
        Dict[str, Dict]: A dictionary of bond angles.
    """
    angles = {}

    for residue in chain.residues:
        angles[residue.residue_code] = BondAngles.bond_angles_of_residue(residue, assign_attribute=assign_attribute, key=key)

    return angles
def bond_angles_of_protein(protein: Protein, assign_attribute: bool = False, key: str = 'bond_angles') ‑> Dict[str, Dict]

Returns the bond angles of a protein

Args

protein : Protein
The protein for which to return the bond angles
assign_attribute : bool
Whether to assign the bond angles to the protein
key : str
The key to use to access the bond angles

Returns

Dict[str, Dict]
A dictionary of bond angles.
Expand source code
@staticmethod
def bond_angles_of_protein(protein: Protein,
                           assign_attribute: bool = False,
                           key: str = "bond_angles") -> Dict[str, Dict]:
    """
    Returns the bond angles of a protein

    Args:
        protein (Protein): The protein for which to return the bond angles
        assign_attribute (bool): Whether to assign the bond angles to the protein
        key (str): The key to use to access the bond angles

    Returns:
        Dict[str, Dict]: A dictionary of bond angles.
    """
    angles = {}

    for chain in protein.chains:
        angles[chain.chain_id] = BondAngles.bond_angles_of_chain(chain, assign_attribute=assign_attribute, key=key)

    return angles
def bond_angles_of_residue(residue: Residue, assign_attribute: bool = False, key: str = 'bond_angles') ‑> Dict[Tuple[str, str, str], float]

Returns the bond angles of a residue

Args

residue : Residue
The residue for which to return the bond angles
assign_attribute : bool
Whether to assign the bond angles to the residue
key : str
The key to use to access the bond angles

Returns

Dict[Tuple[str, str, str], float]
A dictionary of bond angles
Expand source code
@staticmethod
def bond_angles_of_residue(residue: Residue,
                           assign_attribute: bool = False,
                           key: str = "bond_angles") -> Dict[Tuple[str, str, str], float]:
    """
    Returns the bond angles of a residue

    Args:
        residue (Residue): The residue for which to return the bond angles
        assign_attribute (bool): Whether to assign the bond angles to the residue
        key (str): The key to use to access the bond angles

    Returns:
        Dict[Tuple[str, str, str], float]: A dictionary of bond angles
    """
    angles = {}
    defined_angles = BondAngles.HEAVY_ATOM_BOND_ANGLES.get(residue.residue_type, set())

    for (atom1, atom2, atom3) in defined_angles:
        a1 = residue.get_atom(atom1)
        a2 = residue.get_atom(atom2)
        a3 = residue.get_atom(atom3)
        if a1 and a2 and a3:
            angles[(atom1, atom2, atom3)] = BondAngles.angle(a1, a2, a3)
        else:
            angles[(atom1, atom2, atom3)] = None

    if assign_attribute:
        residue.set_attribute(key, angles)

    return angles