def exercise(): from mmtbx.ions import server as s import iotbx.pdb.hierarchy import iotbx.pdb from cctbx.eltbx import chemical_elements # Assert that valence parameters exist for all common ions with their # coordinating atoms for elem in [ "NA", "MG", "K", "CA", "MN", "FE", "NI", "CO", "CU", "ZN", "CD" ]: params = s.get_metal_parameters(elem) assert (len(params.allowed_coordinating_atoms) > 0) assert (params.charge is not None) for elem2 in params.allowed_coordinating_atoms: atom1 = iotbx.pdb.hierarchy.atom() atom1.name = elem atom1.element = elem atom1.charge = "+" + str(params.charge) atom2 = iotbx.pdb.hierarchy.atom() atom2.name = elem2 atom2.element = elem2 atom2.charge = "{:+}".format(s.get_charge(elem2)) assert (s.get_valence_params(atom1, atom2) != (None, None)) # Make sure we don't crash on any ion residue names for elem in chemical_elements.proper_upper_list(): s.get_element(elem) s.get_charge(elem) # Make sure we don't crash on any common residue names either from mmtbx import monomer_library from mmtbx.rotamer.rotamer_eval import mon_lib_query mon_lib_srv = monomer_library.server.server() common_residues = [ getattr(iotbx.pdb, i) for i in dir(iotbx.pdb) if i.startswith("common_residue_names") and isinstance(getattr(iotbx.pdb, i), list) ] common_atoms = { "H": -1, "C": 4, "N": -3, "O": -2, "S": -2, } for common in common_residues: for resn in common: mlq = mon_lib_query(resn, mon_lib_srv) if mlq is None: continue for atom in mlq.atom_dict().values(): elem, charge = s._get_charge_params(resname=resn, element=atom.type_symbol) from libtbx import group_args class GAtom(group_args): def fetch_labels(self): return group_args(resname=self.resname) def id_str(self): return "gatom=\"{} {}\"".format( self.resname, self.element) def charge_as_int(self): return int(self.charge) gatom = GAtom( element=atom.type_symbol, resname=resn, charge="0", ) get_charge_val, get_elem_val = s.get_charge( gatom), s.get_element(gatom) if elem in common_atoms: assert charge == common_atoms[elem] assert get_charge_val == charge assert get_elem_val == elem # And we support all waters for resn in iotbx.pdb.common_residue_names_water: assert s.get_element(resn) == "O" assert s.get_charge(resn) == -2 print "OK"
def _is_favorable_halide_environment( chem_env, scatter_env, assume_hydrogens_all_missing=Auto, ): assume_hydrogens_all_missing = True atom = chem_env.atom binds_amide_hydrogen = False near_cation = False near_lys = False near_hydroxyl = False xyz = col(atom.xyz) min_distance_to_cation = None min_distance_to_hydroxyl = min_distance_to_cation for contact in chem_env.contacts: other = contact.atom resname = contact.resname() atom_name = contact.atom_name() element = contact.element distance = abs(contact) # XXX need to figure out exactly what this should be - CL has a # fairly large radius though (1.67A according to ener_lib.cif) if distance < 2.5: return False if not element in ["C", "N", "H", "O", "S"]: charge = server.get_charge(element) if charge < 0 and distance <= 3.5: # Nearby anion that is too close return False if charge > 0 and distance <= 3.5: # Nearby cation near_cation = True if min_distance_to_cation is None or \ distance < min_distance_to_cation: min_distance_to_cation = distance # Lysine sidechains (can't determine planarity) elif (atom_name in ["NZ"] and #, "NE", "NH1", "NH2"] and resname in ["LYS"] and distance <= 3.5): near_lys = True if min_distance_to_cation is None or \ distance < min_distance_to_cation: min_distance_to_cation = distance # sidechain amide groups, no hydrogens (except Arg) # XXX this would be more reliable if we also calculate the expected # hydrogen positions and use the vector method below elif (atom_name in ["NZ", "NH1", "NH2", "ND2", "NE2"] and resname in ["ARG", "ASN", "GLN"] and (assume_hydrogens_all_missing or resname == "ARG") and distance <= 3.5): binds_amide_hydrogen = True if resname == "ARG" and ( min_distance_to_cation is None or distance < min_distance_to_cation): min_distance_to_cation = distance # hydroxyl groups - note that the orientation of the hydrogen is usually # arbitrary and we can't determine precise bonding elif ((atom_name in ["OG1", "OG2", "OH1"]) and (resname in ["SER", "THR", "TYR"]) and (distance <= 3.5)): near_hydroxyl = True if distance < min_distance_to_hydroxyl: min_distance_to_hydroxyl = distance # Backbone amide, implicit H elif atom_name in ["N"] and assume_hydrogens_all_missing: binds_amide_hydrogen = True # xyz_n = col(contact.site_cart) # bonded_atoms = connectivity[j_seq] # ca_same = c_prev = None # for k_seq in bonded_atoms: # other2 = pdb_atoms[k_seq] # if other2.name.strip().upper() in ["CA"]: # ca_same = col(get_site(k_seq)) # elif other2.name.strip().upper() in ["C"]: # c_prev = col(get_site(k_seq)) # if ca_same is not None and c_prev is not None: # xyz_cca = (ca_same + c_prev) / 2 # vec_ncca = xyz_n - xyz_cca # # 0.86 is the backbone N-H bond distance in geostd # xyz_h = xyz_n + (vec_ncca.normalize() * 0.86) # vec_nh = xyz_n - xyz_h # vec_nx = xyz_n - xyz # angle = abs(vec_nh.angle(vec_nx, deg=True)) # if abs(angle - 180) <= 20: # binds_amide_hydrogen = True # now check again for negatively charged sidechain (etc.) atoms (e.g. # carboxyl groups), but with some leeway if a cation is also nearby. # backbone carbonyl atoms are also excluded. for contact in chem_env.contacts: if contact.altloc() not in ["", "A"]: continue resname = contact.resname() atom_name = contact.atom_name() distance = abs(contact) if ((distance < 3.2) and (min_distance_to_cation is not None and distance < (min_distance_to_cation + 0.2)) and halides.is_negatively_charged_oxygen(atom_name, resname)): return False return binds_amide_hydrogen or near_cation or near_lys
def exercise () : from mmtbx.ions import server as s import iotbx.pdb.hierarchy import iotbx.pdb from cctbx.eltbx import chemical_elements # Assert that valence parameters exist for all common ions with their # coordinating atoms for elem in ["NA", "MG", "K", "CA", "MN", "FE", "NI", "CO", "CU", "ZN", "CD"]: params = s.get_metal_parameters(elem) assert (len(params.allowed_coordinating_atoms) > 0) assert (params.charge is not None) for elem2 in params.allowed_coordinating_atoms : atom1 = iotbx.pdb.hierarchy.atom() atom1.name = elem atom1.element = elem atom1.charge = "+" + str(params.charge) atom2 = iotbx.pdb.hierarchy.atom() atom2.name = elem2 atom2.element = elem2 atom2.charge = "{:+}".format(s.get_charge(elem2)) assert (s.get_valence_params(atom1, atom2) != (None, None)) # Make sure we don't crash on any ion residue names for elem in chemical_elements.proper_upper_list(): s.get_element(elem) s.get_charge(elem) # Make sure we don't crash on any common residue names either from mmtbx import monomer_library from mmtbx.rotamer.rotamer_eval import mon_lib_query mon_lib_srv = monomer_library.server.server() common_residues = [getattr(iotbx.pdb, i) for i in dir(iotbx.pdb) if i.startswith("common_residue_names") and isinstance(getattr(iotbx.pdb, i), list)] common_atoms = { "H": -1, "C": 4, "N": -3, "O": -2, "S": -2, } for common in common_residues: for resn in common: mlq = mon_lib_query(resn, mon_lib_srv) if mlq is None: continue for atom in mlq.atom_dict().values(): elem, charge = s._get_charge_params(resname = resn, element = atom.type_symbol) from libtbx import group_args class GAtom(group_args): def fetch_labels(self): return group_args(resname = self.resname) def id_str(self): return "gatom=\"{} {}\"".format(self.resname, self.element) def charge_as_int(self): return int(self.charge) gatom = GAtom( element = atom.type_symbol, resname = resn, charge = "0", ) get_charge_val, get_elem_val = s.get_charge(gatom), s.get_element(gatom) if elem in common_atoms: assert charge == common_atoms[elem] assert get_charge_val == charge assert get_elem_val == elem # And we support all waters for resn in iotbx.pdb.common_residue_names_water: assert s.get_element(resn) == "O" assert s.get_charge(resn) == -2 print "OK"
def is_favorable_halide_environment(i_seq, contacts, pdb_atoms, sites_frac, connectivity, unit_cell, params, assume_hydrogens_all_missing=Auto): """ Detects if an atom's site exists in a favorable environment for a halide ion. This includes coordinating by a positively charged sidechain or backbone as well as an absense of negatively charged coordinating groups. Parameters ---------- i_seq : int contacts : list of mmtbx.ions.environment.atom_contact pdb_atoms : iotbx.pdb.hierarchy.af_shared_atom sites_frac : tuple of float, float, float connectivity : scitbx.array_family.shared.stl_set_unsigned unit_cell : uctbx.unit_cell params : libtbx.phil.scope_extract assume_hydrogens_all_missing : bool, optional Returns ------- bool """ if (assume_hydrogens_all_missing in [None, Auto]): elements = pdb_atoms.extract_element() assume_hydrogens_all_missing = not ("H" in elements or "D" in elements) atom = pdb_atoms[i_seq] binds_amide_hydrogen = False near_cation = False near_lys = False near_hydroxyl = False xyz = col(atom.xyz) min_distance_to_cation = max(unit_cell.parameters()[0:3]) min_distance_to_hydroxyl = min_distance_to_cation for contact in contacts: # to analyze local geometry, we use the target site mapped to be in the # same ASU as the interacting site def get_site(k_seq): return unit_cell.orthogonalize(site_frac=(contact.rt_mx * sites_frac[k_seq])) other = contact.atom resname = contact.resname() atom_name = contact.atom_name() element = contact.element distance = abs(contact) j_seq = other.i_seq # XXX need to figure out exactly what this should be - CL has a # fairly large radius though (1.67A according to ener_lib.cif) if (distance < params.min_distance_to_other_sites): return False if not element in ["C", "N", "H", "O", "S"]: charge = server.get_charge(element) if charge < 0 and distance <= params.min_distance_to_anion: # Nearby anion that is too close return False if charge > 0 and distance <= params.max_distance_to_cation: # Nearby cation near_cation = True if (distance < min_distance_to_cation): min_distance_to_cation = distance # Lysine sidechains (can't determine planarity) elif (atom_name in ["NZ"] and #, "NE", "NH1", "NH2"] and resname in ["LYS"] and distance <= params.max_distance_to_cation): near_lys = True if (distance < min_distance_to_cation): min_distance_to_cation = distance # sidechain amide groups, no hydrogens (except Arg) # XXX this would be more reliable if we also calculate the expected # hydrogen positions and use the vector method below elif (atom_name in ["NZ", "NH1", "NH2", "ND2", "NE2"] and resname in ["ARG", "ASN", "GLN"] and (assume_hydrogens_all_missing or resname == "ARG") and distance <= params.max_distance_to_cation): if (_is_coplanar_with_sidechain( atom, other.parent(), distance_cutoff=params.max_deviation_from_plane)): binds_amide_hydrogen = True if (resname == "ARG") and (distance < min_distance_to_cation): min_distance_to_cation = distance # hydroxyl groups - note that the orientation of the hydrogen is usually # arbitrary and we can't determine precise bonding elif ((atom_name in ["OG1", "OG2", "OH1"]) and (resname in ["SER", "THR", "TYR"]) and (distance <= params.max_distance_to_hydroxyl)): near_hydroxyl = True if (distance < min_distance_to_hydroxyl): min_distance_to_hydroxyl = distance # Backbone amide, explicit H elif atom_name in ["H"]: # TODO make this more general for any amide H? xyz_h = col(contact.site_cart) bonded_atoms = connectivity[j_seq] if (len(bonded_atoms) != 1): continue xyz_n = col(get_site(bonded_atoms[0])) vec_hn = xyz_h - xyz_n vec_hx = xyz_h - xyz angle = abs(vec_hn.angle(vec_hx, deg=True)) # If Cl, H, and N line up, Cl binds the amide group if abs(angle - 180) <= params.delta_amide_h_angle: binds_amide_hydrogen = True else: pass #print "%s N-H-X angle: %s" % (atom.id_str(), angle) # Backbone amide, implicit H elif atom_name in ["N"] and assume_hydrogens_all_missing: xyz_n = col(contact.site_cart) bonded_atoms = connectivity[j_seq] ca_same = c_prev = None for k_seq in bonded_atoms: other2 = pdb_atoms[k_seq] if other2.name.strip().upper() in ["CA"]: ca_same = col(get_site(k_seq)) elif other2.name.strip().upper() in ["C"]: c_prev = col(get_site(k_seq)) if ca_same is not None and c_prev is not None: xyz_cca = (ca_same + c_prev) / 2 vec_ncca = xyz_n - xyz_cca # 0.86 is the backbone N-H bond distance in geostd xyz_h = xyz_n + (vec_ncca.normalize() * 0.86) vec_nh = xyz_n - xyz_h vec_nx = xyz_n - xyz angle = abs(vec_nh.angle(vec_nx, deg=True)) if abs(angle - 180) <= params.delta_amide_h_angle: binds_amide_hydrogen = True # sidechain NH2 groups, explicit H elif ((atom_name in ["HD1", "HD2"] and resname in ["ASN"]) or (atom_name in ["HE1", "HE2"] and resname in ["GLN"])): # XXX not doing this for Arg because it can't handle the bidentate # coordination #(atom_name in ["HH11","HH12","HH21","HH22"] and resname == "ARG")): bonded_atoms = connectivity[j_seq] assert (len(bonded_atoms) == 1) xyz_n = col(get_site(bonded_atoms[0])) xyz_h = col(contact.site_cart) vec_nh = xyz_n - xyz_h vec_xh = xyz - xyz_h angle = abs(vec_nh.angle(vec_xh, deg=True)) if abs(angle - 180) <= params.delta_amide_h_angle: binds_amide_hydrogen = True else: pass #print "%s amide angle: %s" % (atom.id_str(), angle) # now check again for negatively charged sidechain (etc.) atoms (e.g. # carboxyl groups), but with some leeway if a cation is also nearby. # backbone carbonyl atoms are also excluded. for contact in contacts: if (contact.altloc() not in ["", "A"]): continue resname = contact.resname() atom_name = contact.atom_name() distance = abs(contact) if ((distance < 3.2) and (distance < (min_distance_to_cation + 0.2)) and is_negatively_charged_oxygen(atom_name, resname)): #print contact.id_str(), distance return False return (binds_amide_hydrogen or near_cation or near_lys)
def is_favorable_halide_environment ( i_seq, contacts, pdb_atoms, sites_frac, connectivity, unit_cell, params, assume_hydrogens_all_missing=Auto) : """ Detects if an atom's site exists in a favorable environment for a halide ion. This includes coordinating by a positively charged sidechain or backbone as well as an absense of negatively charged coordinating groups. Parameters ---------- i_seq : int contacts : list of mmtbx.ions.environment.atom_contact pdb_atoms : iotbx.pdb.hierarchy.af_shared_atom sites_frac : tuple of float, float, float connectivity : scitbx.array_family.shared.stl_set_unsigned unit_cell : uctbx.unit_cell params : libtbx.phil.scope_extract assume_hydrogens_all_missing : bool, optional Returns ------- bool """ if (assume_hydrogens_all_missing in [None, Auto]) : elements = pdb_atoms.extract_element() assume_hydrogens_all_missing = not ("H" in elements or "D" in elements) atom = pdb_atoms[i_seq] binds_amide_hydrogen = False near_cation = False near_lys = False near_hydroxyl = False xyz = col(atom.xyz) min_distance_to_cation = max(unit_cell.parameters()[0:3]) min_distance_to_hydroxyl = min_distance_to_cation for contact in contacts : # to analyze local geometry, we use the target site mapped to be in the # same ASU as the interacting site def get_site (k_seq) : return unit_cell.orthogonalize( site_frac = (contact.rt_mx * sites_frac[k_seq])) other = contact.atom resname = contact.resname() atom_name = contact.atom_name() element = contact.element distance = abs(contact) j_seq = other.i_seq # XXX need to figure out exactly what this should be - CL has a # fairly large radius though (1.67A according to ener_lib.cif) if (distance < params.min_distance_to_other_sites) : return False if not element in ["C", "N", "H", "O", "S"]: charge = server.get_charge(element) if charge < 0 and distance <= params.min_distance_to_anion: # Nearby anion that is too close return False if charge > 0 and distance <= params.max_distance_to_cation: # Nearby cation near_cation = True if (distance < min_distance_to_cation) : min_distance_to_cation = distance # Lysine sidechains (can't determine planarity) elif (atom_name in ["NZ"] and #, "NE", "NH1", "NH2"] and resname in ["LYS"] and distance <= params.max_distance_to_cation): near_lys = True if (distance < min_distance_to_cation) : min_distance_to_cation = distance # sidechain amide groups, no hydrogens (except Arg) # XXX this would be more reliable if we also calculate the expected # hydrogen positions and use the vector method below elif (atom_name in ["NZ","NH1","NH2","ND2","NE2"] and resname in ["ARG","ASN","GLN"] and (assume_hydrogens_all_missing or resname == "ARG") and distance <= params.max_distance_to_cation) : if (_is_coplanar_with_sidechain(atom, other.parent(), distance_cutoff = params.max_deviation_from_plane)) : binds_amide_hydrogen = True if (resname == "ARG") and (distance < min_distance_to_cation) : min_distance_to_cation = distance # hydroxyl groups - note that the orientation of the hydrogen is usually # arbitrary and we can't determine precise bonding elif ((atom_name in ["OG1", "OG2", "OH1"]) and (resname in ["SER", "THR", "TYR"]) and (distance <= params.max_distance_to_hydroxyl)) : near_hydroxyl = True if (distance < min_distance_to_hydroxyl) : min_distance_to_hydroxyl = distance # Backbone amide, explicit H elif atom_name in ["H"]: # TODO make this more general for any amide H? xyz_h = col(contact.site_cart) bonded_atoms = connectivity[j_seq] if (len(bonded_atoms) != 1) : continue xyz_n = col(get_site(bonded_atoms[0])) vec_hn = xyz_h - xyz_n vec_hx = xyz_h - xyz angle = abs(vec_hn.angle(vec_hx, deg = True)) # If Cl, H, and N line up, Cl binds the amide group if abs(angle - 180) <= params.delta_amide_h_angle: binds_amide_hydrogen = True else : pass #print "%s N-H-X angle: %s" % (atom.id_str(), angle) # Backbone amide, implicit H elif atom_name in ["N"] and assume_hydrogens_all_missing: xyz_n = col(contact.site_cart) bonded_atoms = connectivity[j_seq] ca_same = c_prev = None for k_seq in bonded_atoms : other2 = pdb_atoms[k_seq] if other2.name.strip().upper() in ["CA"]: ca_same = col(get_site(k_seq)) elif other2.name.strip().upper() in ["C"]: c_prev = col(get_site(k_seq)) if ca_same is not None and c_prev is not None: xyz_cca = (ca_same + c_prev) / 2 vec_ncca = xyz_n - xyz_cca # 0.86 is the backbone N-H bond distance in geostd xyz_h = xyz_n + (vec_ncca.normalize() * 0.86) vec_nh = xyz_n - xyz_h vec_nx = xyz_n - xyz angle = abs(vec_nh.angle(vec_nx, deg = True)) if abs(angle - 180) <= params.delta_amide_h_angle: binds_amide_hydrogen = True # sidechain NH2 groups, explicit H elif ((atom_name in ["HD1","HD2"] and resname in ["ASN"]) or (atom_name in ["HE1","HE2"] and resname in ["GLN"])) : # XXX not doing this for Arg because it can't handle the bidentate # coordination #(atom_name in ["HH11","HH12","HH21","HH22"] and resname == "ARG")): bonded_atoms = connectivity[j_seq] assert (len(bonded_atoms) == 1) xyz_n = col(get_site(bonded_atoms[0])) xyz_h = col(contact.site_cart) vec_nh = xyz_n - xyz_h vec_xh = xyz - xyz_h angle = abs(vec_nh.angle(vec_xh, deg = True)) if abs(angle - 180) <= params.delta_amide_h_angle: binds_amide_hydrogen = True else : pass #print "%s amide angle: %s" % (atom.id_str(), angle) # now check again for negatively charged sidechain (etc.) atoms (e.g. # carboxyl groups), but with some leeway if a cation is also nearby. # backbone carbonyl atoms are also excluded. for contact in contacts : if (contact.altloc() not in ["", "A"]) : continue resname = contact.resname() atom_name = contact.atom_name() distance = abs(contact) if ((distance < 3.2) and (distance < (min_distance_to_cation + 0.2)) and is_negatively_charged_oxygen(atom_name, resname)) : #print contact.id_str(), distance return False return (binds_amide_hydrogen or near_cation or near_lys)
def _is_favorable_halide_environment( chem_env, scatter_env, assume_hydrogens_all_missing=Auto, ): assume_hydrogens_all_missing = True atom = chem_env.atom binds_amide_hydrogen = False near_cation = False near_lys = False near_hydroxyl = False xyz = col(atom.xyz) min_distance_to_cation = None min_distance_to_hydroxyl = min_distance_to_cation for contact in chem_env.contacts: other = contact.atom resname = contact.resname() atom_name = contact.atom_name() element = contact.element distance = abs(contact) # XXX need to figure out exactly what this should be - CL has a # fairly large radius though (1.67A according to ener_lib.cif) if distance < 2.5: return False if not element in ["C", "N", "H", "O", "S"]: charge = server.get_charge(element) if charge < 0 and distance <= 3.5: # Nearby anion that is too close return False if charge > 0 and distance <= 3.5: # Nearby cation near_cation = True if min_distance_to_cation is None or \ distance < min_distance_to_cation: min_distance_to_cation = distance # Lysine sidechains (can't determine planarity) elif (atom_name in ["NZ"] and #, "NE", "NH1", "NH2"] and resname in ["LYS"] and distance <= 3.5): near_lys = True if min_distance_to_cation is None or \ distance < min_distance_to_cation: min_distance_to_cation = distance # sidechain amide groups, no hydrogens (except Arg) # XXX this would be more reliable if we also calculate the expected # hydrogen positions and use the vector method below elif (atom_name in ["NZ", "NH1", "NH2", "ND2", "NE2"] and resname in ["ARG", "ASN", "GLN"] and (assume_hydrogens_all_missing or resname == "ARG") and distance <= 3.5): binds_amide_hydrogen = True if resname == "ARG" and (min_distance_to_cation is None or distance < min_distance_to_cation): min_distance_to_cation = distance # hydroxyl groups - note that the orientation of the hydrogen is usually # arbitrary and we can't determine precise bonding elif ((atom_name in ["OG1", "OG2", "OH1"]) and (resname in ["SER", "THR", "TYR"]) and (distance <= 3.5)): near_hydroxyl = True if distance < min_distance_to_hydroxyl: min_distance_to_hydroxyl = distance # Backbone amide, implicit H elif atom_name in ["N"] and assume_hydrogens_all_missing: binds_amide_hydrogen = True # xyz_n = col(contact.site_cart) # bonded_atoms = connectivity[j_seq] # ca_same = c_prev = None # for k_seq in bonded_atoms: # other2 = pdb_atoms[k_seq] # if other2.name.strip().upper() in ["CA"]: # ca_same = col(get_site(k_seq)) # elif other2.name.strip().upper() in ["C"]: # c_prev = col(get_site(k_seq)) # if ca_same is not None and c_prev is not None: # xyz_cca = (ca_same + c_prev) / 2 # vec_ncca = xyz_n - xyz_cca # # 0.86 is the backbone N-H bond distance in geostd # xyz_h = xyz_n + (vec_ncca.normalize() * 0.86) # vec_nh = xyz_n - xyz_h # vec_nx = xyz_n - xyz # angle = abs(vec_nh.angle(vec_nx, deg=True)) # if abs(angle - 180) <= 20: # binds_amide_hydrogen = True # now check again for negatively charged sidechain (etc.) atoms (e.g. # carboxyl groups), but with some leeway if a cation is also nearby. # backbone carbonyl atoms are also excluded. for contact in chem_env.contacts: if contact.altloc() not in ["", "A"]: continue resname = contact.resname() atom_name = contact.atom_name() distance = abs(contact) if ((distance < 3.2) and (min_distance_to_cation is not None and distance < (min_distance_to_cation + 0.2)) and halides.is_negatively_charged_oxygen(atom_name, resname)): return False return binds_amide_hydrogen or near_cation or near_lys