def qd_opt(mol: Molecule, jobs: Tuple[Optional[Type[Job]], ...], settings: Tuple[Optional[Settings], ...], use_ff: bool = False) -> None: """Perform an optimization of the quantum dot. Performs an inplace update of **mol**. Parameters ---------- mol : |plams.Molecule|_ The to-be optimized molecule. job_recipe : |plams.Settings|_ A Settings instance containing all jon settings. Expects 4 keys: ``"job1"``, ``"job2"``, ``"s1"``, ``"s2"``. forcefield : bool If ``True``, perform the job with CP2K with a user-specified forcefield. """ # Prepare the job settings if use_ff: qd_opt_ff(mol, jobs, settings) return None # Expand arguments job1, job2 = jobs s1, s2 = settings # Extra options for AMSJob if job1 is AMSJob: s1 = Settings(s1) s1.input.ams.constraints.atom = mol.properties.indices if job2 is AMSJob: s2 = Settings(s2) s2.input.ams.constraints.atom = mol.properties.indices # Run the first job and fix broken angles mol.job_geometry_opt(job1, s1, name='QD_opt_part1') fix_carboxyl(mol) fix_h(mol) mol.round_coords() # Run the second job if job2 is not None: mol.job_geometry_opt(job2, s2, name='QD_opt_part2') mol.round_coords() return None
def qd_opt_ff(mol: Molecule, jobs: Tuple[Optional[Type[Job]], ...], settings: Tuple[Optional[Settings], ...], name: str = 'QD_opt', new_psf: bool = False, job_func: Callable = Molecule.job_geometry_opt) -> Results: """Alternative implementation of :func:`.qd_opt` using CP2Ks' classical forcefields. Performs an inplace update of **mol**. Parameters ---------- mol : |plams.Molecule|_ The to-be optimized molecule. jobs : :class:`tuple` A tuple of |plams.Job| types and/or ``None``. settings : :class:`tuple` A tuple of |plams.Settings| types and/or ``None``. name : str The name of the job. See Also -------- :func:`CAT.attachment.qd_opt.qd_opt` Default workflow for optimizing molecules. """ psf_name = os.path.join(mol.properties.path, mol.properties.name + '.psf') # Prepare the job settings job = jobs[0] if isinstance(jobs, abc.Sequence) else jobs s = Settings(settings[0]) if isinstance( settings, abc.Sequence) else Settings(settings) s.runscript.pre = (f'ln "{psf_name}" ./"{name}.psf"\n' f'ln "{mol.properties.prm}" ./"{name}.prm"') s.input.force_eval.subsys.topology.conn_file_name = f'{name}.psf' s.input.force_eval.mm.forcefield.parm_file_name = f'{name}.prm' set_cp2k_element(s, mol) if not os.path.isfile(psf_name) or new_psf: psf = get_psf(mol, s.input.force_eval.mm.forcefield.get('charge', None)) for at, charge, symbol in zip(mol, psf.charge, psf.atom_type): at.properties.charge_float = charge at.properties.symbol = symbol psf.write(psf_name) # Pull any missing non-covalent parameters from UFF if s.input.force_eval.mm.forcefield.nonbonded.get('lennard-jones', None): try: finalize_lj( mol, s.input.force_eval.mm.forcefield.nonbonded['lennard-jones']) except TypeError: pass results = job_func(mol, job, s, name=name, read_template=False, ret_results=True) mol.round_coords() return results
def _asa_plams_ff( mol_complete: Molecule, ligands: Iterable[Molecule], core: Molecule, read_template: bool, job: Type[Job], settings: Settings) -> Tuple[float, float, float, float, int]: """Perform an activation strain analyses with custom Job, Settings and forcefield. Parameters ---------- mol_complete : |plams.Molecule| A Molecule representing the (unfragmented) relaxed structure of the system of interest. ligands : :class:`Iterable<collections.abc.Iterable>` [|plams.Molecule|] An iterable of Molecules containing all ligands in mol_complete. core : |plams.Molecule|, optional The core molecule from **mol_complete**. job : :class:`type` [|plams.Job|] The Job type for the ASA calculations. settings : |plams.Settings| The Job Settings for the ASA calculations. Returns ------- :class:`float`, :class:`float`, :class:`float`, :class:`float` and :class:`int` The energy of **mol_complete**, the energy of **ligands**, the energy of **core**, the energy of an optimized fragment within **ligands** and the total number of fragments within **ligands**. """ s = Settings(settings) # Calculate the (summed) energy of each individual ligand fragment in the total system E_ligands = 0.0 E_min = np.inf mol_min = None for ligand_count, mol in enumerate(ligands, 1): mol.round_coords() qd_opt_ff(mol, job, s, name='ASA_sp', new_psf=True, job_func=Molecule.job_single_point) E = mol.properties.energy.E E_ligands += E if E is not None else np.nan if E < E_min: E_min, mol_min = E, mol # Calculate the energy of the core fragment core.round_coords() qd_opt_ff(core, job, s, name='ASA_sp', new_psf=True, job_func=Molecule.job_single_point) E_core = core.properties.energy.E # Calculate the energy of the total system mol_complete.round_coords() qd_opt_ff(mol_complete, job, s, name='ASA_sp', new_psf=True, job_func=Molecule.job_single_point) E_complete = mol_complete.properties.energy.E # One of the calculations failed; better stop now if np.isnan(E_ligands): return np.nan, np.nan, np.nan, np.nan, ligand_count # Calculate the energy of an optimized fragment s.input.motion.geo_opt.soft_update({ 'type': 'minimization', 'optimizer': 'LBFGS', 'max_iter': 1000, 'lbfgs': { 'max_h_rank': 100 } }) s.input['global'].run_type = 'geometry_optimization' qd_opt_ff(mol_min, job, s, name='ASA_opt') E_ligand_opt = mol_min.properties.energy.E return E_complete, E_ligands, E_core, E_ligand_opt, ligand_count
def _asa_plams(mol_complete: Molecule, ligands: Iterable[Molecule], core: Molecule, read_template: bool, job: Type[Job], settings: Settings) -> Tuple[float, float, float, float, int]: """Perform an activation strain analyses with custom Job and Settings. Parameters ---------- mol_complete : |plams.Molecule| A Molecule representing the (unfragmented) relaxed structure of the system of interest. ligands : :class:`Iterable<collections.abc.Iterable>` [|plams.Molecule|] An iterable of Molecules containing all ligands in mol_complete. core : |plams.Molecule|, optional The core molecule from **mol_complete**. job : :class:`type` [|plams.Job|] The Job type for the ASA calculations. settings : |plams.Settings| The Job Settings for the ASA calculations. Returns ------- :class:`float`, :class:`float`, :class:`float`, :class:`float` and :class:`int` The energy of **mol_complete**, the energy of **ligands**, the energy of **core**, the energy of an optimized fragment within **ligands** and the total number of fragments within **ligands**. """ s = settings # Calculate the energy of the total system mol_complete.round_coords() mol_complete.properties.name += '_frags' mol_complete.job_single_point(job, s) E_complete = mol_complete.properties.energy.E # Calculate the (summed) energy of each individual fragment in the total system E_ligands = 0.0 E_min = np.inf mol_min = None for ligand_count, mol in enumerate(ligands, 1): mol.round_coords() mol.job_single_point(job, s) E = mol.properties.energy.E E_ligands += E if E < E_min: E_min, mol_min = E, mol # One of the calculations failed; better stop now if np.isnan(E_ligands): return np.nan, np.nan, np.nan, np.nan, ligand_count # Calculate the energy of the core core.job_single_point(job, s) E_core = mol.properties.energy.E # Calculate the energy of an optimizes fragment mol_min.job_geometry_opt(job, s) E_ligand_opt = mol_min.properties.energy.E return E_complete, E_ligands, E_core, E_ligand_opt, ligand_count