def modified_minimum_scan_rdkit(ligand: Molecule, bond_tuple: Tuple[int, int], anchor: Atom) -> None: """A modified version of the :func:`.global_minimum_scan_rdkit` function. * Uses the ligand vector as criteria rather than the energy. * Geometry optimizations are constrained during the conformation search. * Finish with a final unconstrained geometry optimization. See Also -------- :func:`global_minimum_scan_rdkit<scm.plams.recipes.global_minimum.minimum_scan_rdkit>`: Optimize the molecule (RDKit UFF) with 3 different values for the given dihedral angle and find the lowest energy conformer. :param |Molecule| mol: The input molecule :param tuple bond_tuple: A 2-tuples containing the atomic indices of valid bonds :return |Molecule|: A copy of *mol* with a newly optimized geometry """ # Define a number of variables and create 3 copies of the ligand angles = (-120, 0, 120) mol_list = [ligand.copy() for _ in range(3)] for angle, mol in zip(angles, mol_list): bond = mol[bond_tuple] atom = mol[bond_tuple[0]] mol.rotate_bond(bond, atom, angle, unit='degree') rdmol_list = [molkit.to_rdmol(mol, properties=False) for mol in mol_list] # Optimize the (constrained) geometry for all dihedral angles in angle_list # The geometry that yields the minimum energy is returned fixed = _find_idx(mol, bond) for rdmol in rdmol_list: ff = UFF(rdmol) for f in fixed: ff.AddFixedPoint(f) ff.Minimize() # Find the conformation with the optimal ligand vector cost_list = [] try: i = ligand.atoms.index(anchor) except ValueError: i = -1 # Default to the origin as anchor for rdmol in rdmol_list: xyz = rdmol_as_array(rdmol) if i == -1: # Default to the origin as anchor xyz = np.vstack([xyz, [0, 0, 0]]) rotmat = optimize_rotmat(xyz, i) xyz[:] = xyz @ rotmat.T xyz -= xyz[i] cost = np.exp(xyz[:, 1:]).sum() cost_list.append(cost) # Perform an unconstrained optimization on the best geometry and update the geometry of ligand j = np.argmin(cost_list) rdmol_best = rdmol_list[j] UFF(rdmol).Minimize() ligand.from_rdmol(rdmol_best)
def fix_h(mol: Molecule) -> None: """If a C=C-H angle is smaller than :math:`20` degrees, set it back to :math:`120` degrees. Performs an inplace update of **plams_mol**. Parameters ---------- plams_mol : |plams.Molecule|_ A PLAMS molecule. """ h_list = [ atom for atom in mol if atom.atnum == 1 and 2.0 in [bond.order for bond in mol.neighbors(atom)[0].bonds] ] rdmol = molkit.to_rdmol(mol) conf = rdmol.GetConformer() get_idx = mol.atoms.index set_angle = rdMolTransforms.SetAngleDeg get_angle = rdMolTransforms.GetAngleDeg update = False for atom in h_list: at1 = atom # Central atom at2 = mol.neighbors(at1)[0] # Neighbours at3 = [atom for atom in mol.neighbors(at2) if atom != at1] # Neighbours of neighbours # Create 2 sets of 3 atomic indices for defining angles: at1-at2=at3 idx_tup1 = get_idx(at3[0]), get_idx(at2), get_idx(at1) idx_tup2 = get_idx(at3[1]), get_idx(at2), get_idx(at1) if get_angle(conf, *idx_tup1) <= 20.0: set_angle(conf, *idx_tup1, 120.0) update = True elif get_angle(conf, *idx_tup2) <= 20.0: set_angle(conf, *idx_tup2, 120.0) update = True if update: mol.from_rdmol(rdmol)
def fix_carboxyl(mol: Molecule) -> None: """Resets carboxylate OCO angles if it is smaller than :math:`60` degrees. Performs an inplace update of **plams_mol**. Parameters ---------- plams_mol : |plams.Molecule|_ A PLAMS molecule. """ rdmol = molkit.to_rdmol(mol) conf = rdmol.GetConformer() matches = rdmol.GetSubstructMatches(_CARBOXYLATE) if matches: get_angle = rdMolTransforms.GetAngleDeg set_angle = rdMolTransforms.SetAngleDeg for idx in matches: if get_angle(conf, idx[3], idx[1], idx[0]) < 60: set_angle(conf, idx[2], idx[1], idx[3], 180.0) set_angle(conf, idx[0], idx[1], idx[3], 120.0) mol.from_rdmol(rdmol)