def _weight(atoms, coords3d, indices, f_damping): m, o, p, n = indices rho_mo = Torsion.rho(atoms, coords3d, (m, o)) rho_op = Torsion.rho(atoms, coords3d, (o, p)) rho_pn = Torsion.rho(atoms, coords3d, (p, n)) rad_mop = Bend._calculate(coords3d, (m, o, p)) rad_opn = Bend._calculate(coords3d, (o, p, n)) return ( (rho_mo * rho_op * rho_pn)**(1/3) * (f_damping + (1-f_damping)*sin(rad_mop)) * (f_damping + (1-f_damping)*sin(rad_opn)) )
def get_linear_bend_inds(coords3d, cbm, bends, min_deg=175, max_bonds=4, logger=None): linear_bends = list() complements = list() if min_deg is None: return linear_bends, complements bm = squareform(cbm) for bend in bends: deg = np.rad2deg(Bend._calculate(coords3d, bend)) bonds = sum(bm[bend[1]]) if (deg >= min_deg) and (bonds <= max_bonds): log( logger, f"Bend {bend}={deg:.1f}° is (close to) linear. " "Creating linear bend & complement.", ) linear_bends.append(bend) complements.append(bend) return linear_bends, complements
def get_hydrogen_bond_inds(atoms, coords3d, bond_inds, logger=None): tmp_sets = [frozenset(bi) for bi in bond_inds] # Check for hydrogen bonds as described in [1] A.1 . # Find hydrogens bonded to small electronegative atoms X = (N, O # F, P, S, Cl). hydrogen_inds = [i for i, a in enumerate(atoms) if a.lower() == "h"] x_inds = [ i for i, a in enumerate(atoms) if a.lower() in "n o f p s cl".split() ] hydrogen_bond_inds = list() for h_ind, x_ind in it.product(hydrogen_inds, x_inds): as_set = set((h_ind, x_ind)) if as_set not in tmp_sets: continue # Check if distance of H to another electronegative atom Y is # greater than the sum of their covalent radii but smaller than # the 0.9 times the sum of their van der Waals radii. If the # angle X-H-Y is greater than 90° a hydrogen bond is asigned. y_inds = set(x_inds) - set((x_ind, )) for y_ind in y_inds: y_atom = atoms[y_ind].lower() cov_rad_sum = CR["h"] + CR[y_atom] distance = Stretch._calculate(coords3d, (h_ind, y_ind)) vdw = 0.9 * (VDW_RADII["h"] + VDW_RADII[y_atom]) angle = Bend._calculate(coords3d, (x_ind, h_ind, y_ind)) if (cov_rad_sum < distance < vdw) and (angle > np.pi / 2): hydrogen_bond_inds.append((h_ind, y_ind)) log( logger, f"Detected hydrogen bond between atoms {h_ind} " f"({atoms[h_ind]}) and {y_ind} ({atoms[y_ind]})", ) return hydrogen_bond_inds
def test_bend(deg): indices = [1, 0, 2] zmat_str = f""" C C 1 1.5 C 1 1.5 2 {deg} """.strip() zmat = zmat_from_str(zmat_str) geom = geom_from_zmat(zmat) coords3d = geom.coords3d # Explicitly implemented # Gradient returned in order [0, 1, 2] val, grad = Bend._calculate(coords3d, indices, gradient=True) # Reference values, code generated args = coords3d[indices].flatten() ref_val = q_a(*args) # Reference gradient returned in order [1, 0, 2] _ref_grad = dq_a(*args) ref_grad = np.zeros_like(coords3d) ref_grad[indices] = _ref_grad.reshape(-1, 3) mp_ref_val = mp_d.q_a(*args) _mp_ref_grad = mp_d.dq_a(*args) mp_ref_grad = np.zeros_like(coords3d) mp_ref_grad[indices] = _mp_ref_grad.reshape(-1, 3) assert val == pytest.approx(ref_val) assert val == pytest.approx(mp_ref_val) np.testing.assert_allclose(grad.flatten(), ref_grad.flatten(), atol=1e-12) np.testing.assert_allclose(grad.flatten(), mp_ref_grad.flatten(), atol=1e-12) # Code generated 2nd derivative dgrad = d2q_a(*args) mp_dgrad = mp_d.d2q_a(*args) # Finite difference reference values ref_dgrad = fin_diff_B(Bend(indices), coords3d) np.testing.assert_allclose(dgrad, ref_dgrad, atol=1e-9) np.testing.assert_allclose(mp_dgrad, ref_dgrad, atol=1e-9)
def get_dihedral_inds(coords3d, bond_inds, bend_inds, max_deg, logger=None): max_rad = np.deg2rad(max_deg) bond_dict = dict() for from_, to_ in bond_inds: bond_dict.setdefault(from_, list()).append(to_) bond_dict.setdefault(to_, list()).append(from_) proper_dihedral_inds = list() improper_candidates = list() improper_dihedral_inds = list() def log_dihed_skip(inds): log( logger, f"Skipping generation of dihedral {inds} " "as some of the the atoms are (close too) linear.", ) def set_dihedral_index(dihedral_ind, proper=True): dihed = tuple(dihedral_ind) check_in = proper_dihedral_inds if proper else improper_dihedral_inds # Check if this dihedral is already present if (dihed in check_in) or (dihed[::-1] in check_in): return # Assure that the angles are below 175° (3.054326 rad) if not dihedral_valid(coords3d, dihedral_ind, deg_thresh=max_deg): log_dihed_skip(dihedral_ind) return if proper: proper_dihedral_inds.append(dihed) else: improper_dihedral_inds.append(dihed) for bond, bend in it.product(bond_inds, bend_inds): # print("bond", bond, "bend", bend) central = bend[1] bend_set = set(bend) bond_set = set(bond) # Check if the two sets share one common atom. If not continue. intersect = bend_set & bond_set # print("intersect", intersect) if len(intersect) != 1: continue # if bond == frozenset((0, 11)) and bend == (0, 3, 4): # import pdb; pdb.set_trace() # pass # TODO: check collinearity of bond and bend. # When the common atom between bond and bend is a terminal, and not a central atom # in the bend we create a proper dihedral. Improper dihedrals are only created # when no proper dihedrals have been found. if central not in bond_set: # The new terminal atom in the dihedral is the one, that doesn' intersect. terminal = tuple(bond_set - intersect)[0] intersecting_atom = tuple(intersect)[0] bend_terminal = tuple(bend_set - {central} - intersect)[0] bend_rad = Bend._calculate(coords3d, bend) # Bend atoms are nearly collinear. Check if we can skip the central bend atom # and use an atom that is conneced to the terminal atom of the bend or bond. if bend_rad >= max_rad: bend_terminal_bonds = set(bond_dict[bend_terminal]) - bend_set bond_terminal_bonds = set(bond_dict[terminal]) - bond_set set_dihedrals = [ (terminal, intersecting_atom, bend_terminal, betb) for betb in bend_terminal_bonds ] + [(bend_terminal, intersecting_atom, terminal, botb) for botb in bond_terminal_bonds] # Hardcoded for now ... look ahead to next shell of atoms if not any([ dihedral_valid(coords3d, inds, deg_thresh=max_deg) for inds in set_dihedrals ]): set_dihedrals = [] for betb in bend_terminal_bonds: bend_terminal_bonds_v2 = set( bond_dict[betb]) - bend_set - bond_set set_dihedrals = [(terminal, intersecting_atom, betb, betb_v2) for betb_v2 in bend_terminal_bonds_v2] for botb in bond_terminal_bonds: bond_terminal_bonds_v2 = set( bond_dict[botb]) - bend_set - bond_set set_dihedrals = [(bend_terminal, intersecting_atom, botb, botb_v2) for botb_v2 in bond_terminal_bonds_v2] elif intersecting_atom == bend[0]: set_dihedrals = [[terminal] + list(bend)] else: set_dihedrals = [list(bend) + [terminal]] [set_dihedral_index(dihed) for dihed in set_dihedrals] # If the common atom is the central atom we try to form an out # of plane bend / improper torsion. They may be created later on. else: fourth_atom = list(bond_set - intersect) dihedral_ind = list(bend) + fourth_atom # This way dihedrals may be generated that contain linear # atoms and these would be undefinied. So we check for this. if dihedral_valid(coords3d, dihedral_ind, deg_thresh=max_deg): improper_candidates.append(dihedral_ind) else: log_dihed_skip(dihedral_ind) # Now try to create the remaining improper dihedrals. if (len(coords3d) >= 4) and (len(proper_dihedral_inds) == 0): log( logger, "Could not define any proper dihedrals! Generating improper dihedrals!", ) for improp in improper_candidates: set_dihedral_index(improp, proper=False) log( logger, "Permutational symmetry not considerd in generation of " "improper dihedrals.", ) return proper_dihedral_inds, improper_dihedral_inds
def bend_valid(coords3d, indices, min_deg, max_deg): val = Bend._calculate(coords3d, indices) deg = np.rad2deg(val) return min_deg <= deg <= max_deg
def bend_still_valid(coords3d, indices, min_deg, max_deg): val = Bend._calculate(coords3d, indices) deg = np.rad2deg(val) # Less than, not less or equal as in "bend_valid" return min_deg <= deg < max_deg