def trigonal(self, atom): """Add hydrogens in trigonal geometry. Args: atom: atom to protonate """ _LOGGER.debug( 'TRIGONAL - {0:d} bonded atoms'.format(len(atom.bonded_atoms)) ) rot_angle = math.radians(120.0) cvec = Vector(atom1=atom) # 0 bonds if len(atom.bonded_atoms) == 0: pass # 1 bond if len(atom.bonded_atoms) == 1 and atom.number_of_protons_to_add > 0: # Add another atom with the right angle to the first one avec = Vector(atom1=atom, atom2=atom.bonded_atoms[0]) # use plane of bonded trigonal atom - e.g. arg self.set_steric_number_and_lone_pairs(atom.bonded_atoms[0]) if (atom.bonded_atoms[0].steric_number == 3 and len(atom.bonded_atoms[0].bonded_atoms) > 1): # use other atoms bonded to the neighbour to establish the # plane, if possible other_atom_indices = [] for i, bonded_atom in enumerate( atom.bonded_atoms[0].bonded_atoms): if bonded_atom != atom: other_atom_indices.append(i) vec1 = Vector(atom1=atom, atom2=atom.bonded_atoms[0]) vec2 = Vector(atom1=atom.bonded_atoms[0], atom2=atom.bonded_atoms[0] .bonded_atoms[other_atom_indices[0]]) axis = vec1**vec2 # this is a trick to make sure that the order of atoms doesn't # influence the final postions of added protons if len(other_atom_indices) > 1: vec3 = Vector(atom1=atom.bonded_atoms[0], atom2=atom.bonded_atoms[0] .bonded_atoms[other_atom_indices[1]]) axis2 = vec1**vec3 if axis*axis2 > 0: axis = axis+axis2 else: axis = axis-axis2 else: axis = avec.orthogonal() avec = rotate_vector_around_an_axis(rot_angle, axis, avec) avec = self.set_bond_distance(avec, atom.element) self.add_proton(atom, cvec+avec) # 2 bonds if len(atom.bonded_atoms) == 2 and atom.number_of_protons_to_add > 0: # Add another atom with the right angle to the first two avec1 = Vector(atom1=atom, atom2=atom.bonded_atoms[0]).rescale(1.0) avec2 = Vector(atom1=atom, atom2=atom.bonded_atoms[1]).rescale(1.0) new_a = -avec1 - avec2 new_a = self.set_bond_distance(new_a, atom.element) self.add_proton(atom, cvec+new_a)
def are_atoms_planar(atoms): """Test whether a group of atoms are planar. Args: atoms: list of atoms Returns: Boolean """ if len(atoms) == 0: return False if len(atoms) < 4: return False vec1 = Vector(atom1=atoms[0], atom2=atoms[1]) vec2 = Vector(atom1=atoms[0], atom2=atoms[2]) norm = (vec1**vec2).rescale(1.0) margin = PLANARITY_MARGIN for atom in atoms[3:]: vec = Vector(atom1=atoms[0], atom2=atom).rescale(1.0) if abs(vec * norm) > margin: return False return True
def tetrahedral(self, atom): """Protonate atom in tetrahedral geometry. Args: atom: atom to protonate. """ debug('TETRAHEDRAL - {0:d} bonded atoms'.format(len( atom.bonded_atoms))) # TODO - might be good to move tetrahedral angle to constant rot_angle = math.radians(109.5) cvec = Vector(atom1=atom) # 0 bonds if len(atom.bonded_atoms) == 0: pass # 1 bond if len(atom.bonded_atoms) == 1 and atom.number_of_protons_to_add > 0: # Add another atom with the right angle to the first one avec = Vector(atom1=atom, atom2=atom.bonded_atoms[0]) axis = avec.orthogonal() avec = rotate_vector_around_an_axis(rot_angle, axis, avec) avec = self.set_bond_distance(avec, atom.element) self.add_proton(atom, cvec + avec) # 2 bonds if len(atom.bonded_atoms) == 2 and atom.number_of_protons_to_add > 0: # Add another atom with the right angle to the first two avec1 = Vector(atom1=atom, atom2=atom.bonded_atoms[0]).rescale(1.0) avec2 = Vector(atom1=atom, atom2=atom.bonded_atoms[1]).rescale(1.0) axis = avec1 + avec2 new_a = rotate_vector_around_an_axis(math.radians(90), axis, -avec1) new_a = self.set_bond_distance(new_a, atom.element) self.add_proton(atom, cvec + new_a) # 3 bonds if len(atom.bonded_atoms) == 3 and atom.number_of_protons_to_add > 0: avec1 = Vector(atom1=atom, atom2=atom.bonded_atoms[0]).rescale(1.0) avec2 = Vector(atom1=atom, atom2=atom.bonded_atoms[1]).rescale(1.0) avec3 = Vector(atom1=atom, atom2=atom.bonded_atoms[2]).rescale(1.0) new_a = -avec1 - avec2 - avec3 new_a = self.set_bond_distance(new_a, atom.element) self.add_proton(atom, cvec + new_a)