def optimize_ligand(ligand: Molecule) -> None: """Optimize a ligand molecule.""" anchor = ligand.properties.dummies # Split the branched ligand into linear fragments and optimize them individually bonds = split_mol(ligand, anchor) context = SplitMol(ligand, bonds) with context as mol_frags: cap_dict = ChainMap(*context._at_pairs) for mol in mol_frags: cap_list = [cap for at, cap in cap_dict.items() if at in mol] mol.set_dihed(180.0, anchor, cap_list) # Find the optimal dihedrals angle between the fragments for bond in bonds: modified_minimum_scan_rdkit(ligand, ligand.get_index(bond), anchor) # RDKit UFF can sometimes mess up the geometries of carboxylates: fix them fix_carboxyl(ligand) # Allign the ligand with the Cartesian X-axis. allign_axis(ligand, anchor)
def ligand_to_qd(core: Molecule, ligand: Molecule, path: str, allignment: str = 'sphere', idx_subset: Optional[Iterable[int]] = None) -> Molecule: """Function that handles quantum dot (qd, *i.e.* core + all ligands) operations. Combine the core and ligands and assign properties to the quantom dot. Parameters ---------- core : |plams.Molecule|_ A core molecule. ligand : |plams.Molecule|_ A ligand molecule. allignment : :class:`str` How the core vector(s) should be defined. Accepted values are ``"sphere"`` and ``"surface"``: * ``"sphere"``: Vectors from the core anchor atoms to the center of the core. * ``"surface"``: Vectors perpendicular to the surface of the core. Note that for a perfect sphere both approaches are equivalent. idx_subset : :class:`Iterable<collections.anc.Iterable>` [:class:`int`], optional An iterable with the (0-based) indices defining a subset of atoms in **core**. Only relevant in the construction of the convex hull when ``allignment=surface``. Returns ------- |plams.Molecule|_ A quantum dot consisting of a core molecule and *n* ligands """ def get_name() -> str: core_name = core.properties.name anchor = str(qd[-1].properties.pdb_info.ResidueNumber - 1) lig_name = ligand.properties.name return f'{core_name}__{anchor}_{lig_name}' idx_subset_ = idx_subset if idx_subset is not None else ... # Define vectors and indices used for rotation and translation the ligands vec1 = np.array([-1, 0, 0], dtype=float) # All ligands are already alligned along the X-axis idx = ligand.get_index(ligand.properties.dummies) - 1 ligand.properties.dummies.properties.anchor = True # Attach the rotated ligands to the core, returning the resulting strucutre (PLAMS Molecule). if allignment == 'sphere': vec2 = np.array(core.get_center_of_mass()) - sanitize_dim_2(core.properties.dummies) vec2 /= np.linalg.norm(vec2, axis=1)[..., None] elif allignment == 'surface': if isinstance(core.properties.dummies, np.ndarray): anchor = core.properties.dummies else: anchor = core.as_array(core.properties.dummies) vec2 = -get_surface_vec(np.array(core)[idx_subset_], anchor) else: raise ValueError(repr(allignment)) lig_array = rot_mol(ligand, vec1, vec2, atoms_other=core.properties.dummies, core=core, idx=idx) qd = core.copy() array_to_qd(ligand, lig_array, mol_out=qd) qd.round_coords() # Set properties qd.properties = Settings({ 'indices': [i for i, at in enumerate(qd, 1) if at.properties.pdb_info.ResidueName == 'COR' or at.properties.anchor], 'path': path, 'name': get_name(), 'job_path': [], 'prm': ligand.properties.get('prm') }) # Print and return _evaluate_distance(qd, qd.properties.name) return qd