#!/usr/bin/env amspython # coding: utf-8 # ## Initial Imports import sys import multiprocessing from scm.plams import JobRunner, config, from_smiles, Settings, AMSJob, init import numpy as np # this line is not required in AMS2025+ init() # ## Set Up Job Runner # Set up job runner, running as many jobs as possible in parallel. config.default_jobrunner = JobRunner(parallel=True, maxjobs=multiprocessing.cpu_count()) config.job.runscript.nproc = 1 # ## Set Up Molecules # Create the molecules we want to use in our benchmark from SMILES. # The molecules we want to use in our benchmark: mol_smiles = {"Methane": "C", "Ethane": "C-C", "Ethylene": "C=C", "Acetylene": "C#C"} molecules = {} for name, smiles in mol_smiles.items(): # Compute 10 conformers, optimize with UFF and pick the lowest in energy. molecules[name] = from_smiles(smiles, nconfs=10, forcefield="uff")[0] print(name, molecules[name]) # ## Initialize Calculation Settings # Set up the settings which are common across jobs. The basis type is added later for each job. common_settings = Settings() common_settings.input.ams.Task = "SinglePoint" common_settings.input.ams.System.Symmetrize = "Yes" common_settings.input.adf.Basis.Core = "None" basis = ["QZ4P", "TZ2P", "TZP", "DZP", "DZ", "SZ"] reference_basis = "QZ4P" # ## Run Calculations results = {} jobs = [] for bas in basis: for name, molecule in molecules.items(): settings = common_settings.copy() settings.input.adf.Basis.Type = bas job = AMSJob(name=name + "_" + bas, molecule=molecule, settings=settings) jobs.append(job) results[(name, bas)] = job.run() # ## Results # Extract the energy from each calculation. Calculate the average absolute error in bond energy per atom for each basis set. try: # For AMS2025+ can use JobAnalysis class to perform results analysis from scm.plams import JobAnalysis ja = ( JobAnalysis(jobs=jobs, standard_fields=["Formula", "Smiles"]) .add_settings_field(("Input", "ADF", "Basis", "Type"), display_name="Basis") .add_field("NAtoms", lambda j: len(j.molecule)) .add_field( "Energy", lambda j: j.results.get_energy(unit="kcal/mol"), display_name="Energy [kcal/mol]", fmt=".2f" ) .sort_jobs(["NAtoms", "Energy"]) ) ref_ja = ja.copy().filter_jobs(lambda data: data["InputAdfBasisType"] == "QZ4P") ref_energies = {f: e for f, e in zip(ref_ja.Formula, ref_ja.Energy)} def get_average_error(job): return abs(job.results.get_energy(unit="kcal/mol") - ref_energies[job.molecule.get_formula()]) / len( job.molecule ) ja.add_field("AvErr", get_average_error, display_name="Average Error [kcal/mol]", fmt=".2f") # Pretty-print if running in a notebook if "ipykernel" in sys.modules: ja.display_table() else: print(ja.to_table()) except ImportError: average_errors = {} for bas in basis: if bas != reference_basis: errors = [] for name, molecule in molecules.items(): reference_energy = results[(name, reference_basis)].get_energy(unit="kcal/mol") energy = results[(name, bas)].get_energy(unit="kcal/mol") errors.append(abs(energy - reference_energy) / len(molecule)) print("Energy for {} using {} basis set: {} [kcal/mol]".format(name, bas, energy)) average_errors[bas] = sum(errors) / len(errors) print("== Results ==") print("Average absolute error in bond energy per atom") for bas in basis: if bas != reference_basis: if ja: av = np.average(ja.copy().filter_jobs(lambda data: data["InputAdfBasisType"] == bas).AvErr) else: av = average_errors[bas] print("Error for basis set {:<4}: {:>10.3f} [kcal/mol]".format(bas, av))