#!/usr/bin/env amspython
# coding: utf-8
# ## Initial Imports
import multiprocessing
import numpy as np
from scm.plams import Settings, Results, MultiJob, JobRunner, config, Molecule, Atom, AMSJob, init
# this line is not required in AMS2025+
init()
# ## Helper Classes
class GammaResults(Results):
@staticmethod
def get_difference(job, jobplus):
"""Calculate the difference between HOMO and IP.
*jobplus* should be the counterpart of *job* with one less electron."""
homo = job.results.readrkf("Properties", "HOMO", file="engine")
IP = jobplus.results.get_energy() - job.results.get_energy()
return IP + homo
def get_J(self):
N = GammaResults.get_difference(self.job.children[1], self.job.children[2])
A = GammaResults.get_difference(self.job.children[0], self.job.children[1])
return (N**2 + A**2) ** 0.5
class GammaJob(MultiJob):
_result_type = GammaResults
def __init__(self, molecule, gamma, charge, spins, **kwargs):
MultiJob.__init__(self, **kwargs)
self.molecule = molecule
self.charge = charge
self.spins = spins
self.gamma = gamma
def prerun(self):
charges = [self.charge - 1, self.charge, self.charge + 1]
for charge, spin in zip(charges, self.spins):
name = "{}_charge_{}".format(self.name, charge)
name = name.replace("-", "minus")
newjob = AMSJob(name=name, molecule=self.molecule, settings=self.settings)
newjob.molecule.properties.charge = charge
newjob.settings.input.adf.xc.rangesep = "gamma={:f}".format(self.gamma)
if spin != 0:
newjob.settings.input.adf.unrestricted = True
newjob.settings.input.adf.SpinPolarization = spin
self.children.append(newjob)
def gamma_scan(gammas, settings, molecule, name="scan", charge=0, spins=(1, 0, 1)):
"""Calculate values of J function for given range of gammas.
Arguments:
gammas - list of gamma values to calculate the J function for
settings - Settings object for an ADF calculation
molecule - Molecule object with the system of interest
name - base name of all the jobs
charge - base charge of the system of interest. The J function is going to be
calculated based on two systems: with charge, and charge-1
spins - values of spin polarization for jobs with, respectively, charge-1,
charge and charge +1
In other words, if charge=X and spins=(a,b,c) the three resulting jobs
are going to have the following values for charge and spin:
Charge=X-1 SpinPolarization=a
Charge=X SpinPolarization=b
Charge=X+1 SpinPolarization=c
Returns a list of pairs (gamma, J) of the same length as the parameter *gammas*
"""
jobs = [
GammaJob(
molecule=molecule, settings=settings, gamma=g, charge=charge, spins=spins, name=name + "_gamma_" + str(g)
)
for g in gammas
]
results = [j.run() for j in jobs]
js = [r.get_J() for r in results]
return list(zip(gammas, js))
# ## Configure Parallel JobRunner
# Set up the default jobrunner to run in parallel, with as many jobs as there are cores.
config.default_jobrunner = JobRunner(parallel=True, maxjobs=multiprocessing.cpu_count())
# ## Settings of the ADF calculations
# Configure settings object for the calculation.
s = Settings()
s.input.ams.task = "SinglePoint"
s.input.adf.basis.type = "DZP"
s.input.adf.basis.core = "None"
s.input.adf.xc.gga = "PBE"
s.input.adf.xc.xcfun = True
s.runscript.nproc = 1
# ## Set Up Molecule
# Create a toy hydrogen dimer.
mol = Molecule()
mol.add_atom(Atom(symbol="H", coords=(0, 0, -0.3540)))
mol.add_atom(Atom(symbol="H", coords=(0, 0, 0.3540)))
# ## Calculate Gamma Values
# Perform a scan of a few values for gamma. In practice, you want to scan a wider range and smaller step.
gammas = np.around(np.arange(1.2, 1.9, 0.2), decimals=3)
results = gamma_scan(gammas, s, mol)
print("== Results ==")
print("gamma \t J")
for g, j in results:
print("{:.4f} \t {:.8f}".format(g, j))
print("Optimal gamma value: {:.4f}".format(min(results, key=lambda x: x[1])[0]))