Complete Python code

#!/usr/bin/env amspython
# coding: utf-8

# ## Purpose
# Example showing the `UseLowestEnergy` feature of the Hybrid engine and the `OptimizeSpinRound` feature of the ADF engine.
#
# A bond scan is performed on hydrogen peroxide with UFF. The resulting
# structures are then recalculated with DFT using four different setups. In
# particular, one setup uses the Hybrid engine with DynamicFactors=UseLowestEnergy.
# Another uses the ADF engine with `OptimizeSpinRound` in combination with an `ElectronicTemperature`.
#
# In the end, the results of energy vs. bond length are plotted.
#
# ## Initial imports

import matplotlib.pyplot as plt
from scm.plams import from_smiles, Settings, AMSJob, Units, init

# this line is not required in AMS2025+
init()


# ## Helper functions


def initial_pesscan():
    """Run a bond scan for hydrogen peroxide with UFF. Returns the finished job"""
    mol = from_smiles("OO")  # hydrogen peroxide
    s = Settings()
    s.input.ams.Task = "PESScan"
    s.input.ams.PESScan.ScanCoordinate.nPoints = 7
    # Scan O-O bond length (atoms 1 and 2) between 1.2 and 2.5 Å
    s.input.ams.PESScan.ScanCoordinate.Distance = "2 1 1.2 2.5"
    s.input.ForceField.Type = "UFF"
    job = AMSJob(settings=s, molecule=mol, name="initial_pesscan")
    job.run()
    return job


def singlet_settings(header=""):
    s = Settings()
    s.Unrestricted = "No"
    s.XC.GGA = "PBE"
    s.Basis.Type = "DZP"
    s.SCF.Iterations = 100
    s._h = header
    return s


def triplet_settings(header=""):
    s = Settings()
    s.Unrestricted = "Yes"
    s.SpinPolarization = 2
    s.XC.GGA = "PBE"
    s.Basis.Type = "DZP"
    s.SCF.Iterations = 100
    s._h = header
    return s


def hybrid_settings():
    """Look at the file plams_workdir/hybrid/hybrid.in to see how the below is translated into text input for the hybrid engine"""
    s = Settings()
    s.input.Hybrid.Energy.DynamicFactors = "UseLowestEnergy"

    s.input.Hybrid.Energy.Term = ["Region=* EngineID=Singlet", "Region=* EngineID=Triplet"]

    s.input.Hybrid.Engine = [singlet_settings("ADF Singlet"), triplet_settings("ADF Triplet")]
    return s


def replay_job(rkf, engine="hybrid"):
    """Replay the structures from the UFF pesscan with ADF. Use three different engines:
    Hybrid: This will run all structures with both Singlet and Triplet and pick the lowest energy
    Singlet: This runs all structures using the Singlet settings
    Triplet: This runs all structures using the Triplet settings

    Returns the finished job
    """
    s = Settings()
    if engine == "hybrid":
        s = hybrid_settings()
    elif engine == "singlet":
        s.input.adf = singlet_settings()
    elif engine == "triplet":
        s.input.adf = triplet_settings()
    elif engine == "optimizespin":
        s.input.adf = triplet_settings()
        s.input.adf.Occupations = "ElectronicTemperature=300 OptimizeSpinRound=0.05"

    s.input.ams.Task = "Replay"
    s.input.ams.Replay.File = rkf

    job = AMSJob(settings=s, name=engine)
    job.run()
    return job


def plot_results(singlet_job, triplet_job, hybrid_job, optimizespin_job):
    """
    Generate a plot of the energy vs. bond length for the three different jobs.  Saves a plot to pesplot.png.
    """
    bondlengths = singlet_job.results.get_pesscan_results()["RaveledPESCoords"][0]
    bondlengths = Units.convert(bondlengths, "bohr", "angstrom")

    singlet_pes = singlet_job.results.get_pesscan_results()["PES"]
    triplet_pes = triplet_job.results.get_pesscan_results()["PES"]
    hybrid_pes = hybrid_job.results.get_pesscan_results()["PES"]
    hybrid_pes = [x - 0.005 for x in hybrid_pes]  # slightly downshift for visual clarity when plotting
    optimizespin_pes = optimizespin_job.results.get_pesscan_results()["PES"]
    optimizespin_pes = [x - 0.010 for x in optimizespin_pes]  # slightly downshift for visual clarity when plotting

    plt.plot(bondlengths, singlet_pes)
    plt.plot(bondlengths, triplet_pes)
    plt.plot(bondlengths, hybrid_pes)
    plt.plot(bondlengths, optimizespin_pes)
    plt.title("PES Scan for hydrogen peroxide")
    plt.xlabel("O-O bond length (Å)")
    plt.ylabel("Energy (hartree)")
    plt.legend(
        ["Singlet", "Triplet", "Hybrid: Lowest energy (slightly shifted)", "ADF: optimized spin (slightly shifted)"]
    )
    plt.savefig("pesplot.png")
    plt.show()


# ## Run the job

pesscan_job = initial_pesscan()
rkf = pesscan_job.results.rkfpath()
singlet_job = replay_job(rkf, "singlet")
triplet_job = replay_job(rkf, "triplet")
hybrid_job = replay_job(rkf, "hybrid")
optimizespin_job = replay_job(rkf, "optimizespin")
# or load the finished jobs from disk:
# pesscan_job = AMSJob.load_external('plams_workdir.002/initial_pesscan/ams.rkf')
# rkf = pesscan_job.results.rkfpath()
# singlet_job = AMSJob.load_external('plams_workdir.002/singlet/ams.rkf')
# triplet_job = AMSJob.load_external('plams_workdir.002/triplet/ams.rkf')
# hybrid_job = AMSJob.load_external('plams_workdir.002/hybrid/ams.rkf')
# optimizespin_job = AMSJob.load_external('plams_workdir.002/optimizespin/ams.rkf')


# ## Plot the result
#
# Two equivalent to follow the lowest energy of the singlet and the triplet state, with either the Hybrid engine (green) or the optimize spin option (red) from ADF.

plot_results(singlet_job, triplet_job, hybrid_job, optimizespin_job)