Ethylene ADF Frontier Orbital Comparison

Requires: AMS2026 or later

Related documentation
../_images/frontier_energy_levels_6c055456.png

PBE and B3LYP frontier-orbital energy levels.

Ethylene was built from the SMILES string C=C and optimized once with ADF at PBE/DZP. Two single-point calculations, PBE/DZP and B3LYP/DZP, then used that same geometry. Both calculations used an all-electron DZP basis and Normal numerical quality. Orbital energies are Kohn-Sham eigenvalues in eV.

Optimized geometry

The optimized C=C distance is 1.3320 Å.

Optimized ethylene geometry

Frontier orbital energies

Method

HOMO (eV)

LUMO (eV)

Gap (eV)

PBE/DZP

-7.0061

-1.1879

5.8182

B3LYP/DZP

-7.7749

-0.2106

7.5642

The nearest three occupied and three virtual orbitals are:

Method

Orbital

Energy (eV)

Occupation

PBE/DZP

HOMO-2

-10.4784

2.0

PBE/DZP

HOMO-1

-8.7810

2.0

PBE/DZP

HOMO

-7.0061

2.0

PBE/DZP

LUMO

-1.1879

0.0

PBE/DZP

LUMO+1

0.8367

0.0

PBE/DZP

LUMO+2

1.9669

0.0

B3LYP/DZP

HOMO-2

-11.7766

2.0

B3LYP/DZP

HOMO-1

-9.9698

2.0

B3LYP/DZP

HOMO

-7.7749

2.0

B3LYP/DZP

LUMO

-0.2106

0.0

B3LYP/DZP

LUMO+1

1.3945

0.0

B3LYP/DZP

LUMO+2

2.5806

0.0

Frontier orbital energy-level diagram

HOMO and LUMO isosurfaces

The four orbital pictures below were generated by amsreport from the corresponding ADF engine result files. Contrasting colors mark opposite orbital phases.

Method

HOMO

LUMO

PBE/DZP

PBE HOMO

PBE LUMO

B3LYP/DZP

B3LYP HOMO

B3LYP LUMO

Conclusion

At this fixed PBE geometry, replacing PBE with B3LYP changes the Kohn-Sham HOMO-LUMO gap by +1.7460 eV. The HOMO and LUMO isosurfaces allow the comparison to distinguish an energy shift from a qualitative change in orbital shape. These Kohn-Sham gaps are method-dependent orbital-energy differences, not optical excitation energies.

Calculation inputs

PBE/DZP geometry optimization

Task GeometryOptimization

System
  Atoms
              C       0.6644850805       0.0279879686      -0.0236852009
              C      -0.6644849893      -0.0279879318       0.0236851499
              H       1.2534332310      -0.8786144292       0.0702991539
              H       1.1670384263       0.9805640030      -0.1565753856
              H      -1.2534332920       0.8786143859      -0.0702991171
              H      -1.1670384566      -0.9805639965       0.1565753997
  End
  BondOrders
     1 2 2.0
     1 3 1.0
     1 4 1.0
     2 5 1.0
     2 6 1.0
  End
End

Engine adf
  Basis
    Core None
    Type DZP
  End
  NumericalQuality Normal
  SCF
    Iterations 100
  End
  XC
    GGA PBE
  End
EndEngine

PBE/DZP single point

Task SinglePoint

System
  Atoms
              C       0.6649813115       0.0278988522      -0.0242986367
              C      -0.6649810155      -0.0278982997       0.0243008447
              H       1.2789279657      -0.8699226278       0.0686049752
              H       1.1932069166       0.9740820859      -0.1564006741
              H      -1.2789292580       0.8699198605      -0.0686220517
              H      -1.1932059203      -0.9740798711       0.1564155425
  End
  BondOrders
     1 2 2.0
     1 3 1.0
     1 4 1.0
     2 5 1.0
     2 6 1.0
  End
End

Engine adf
  Basis
    Core None
    Type DZP
  End
  NumericalQuality Normal
  SCF
    Iterations 100
  End
  XC
    GGA PBE
  End
EndEngine

B3LYP/DZP single point

Task SinglePoint

System
  Atoms
              C       0.6649813115       0.0278988522      -0.0242986367
              C      -0.6649810155      -0.0278982997       0.0243008447
              H       1.2789279657      -0.8699226278       0.0686049752
              H       1.1932069166       0.9740820859      -0.1564006741
              H      -1.2789292580       0.8699198605      -0.0686220517
              H      -1.1932059203      -0.9740798711       0.1564155425
  End
  BondOrders
     1 2 2.0
     1 3 1.0
     1 4 1.0
     2 5 1.0
     2 6 1.0
  End
End

Engine adf
  Basis
    Core None
    Type DZP
  End
  NumericalQuality Normal
  SCF
    Iterations 100
  End
  XC
    Hybrid B3LYP
  End
EndEngine

Prompts and Python scripts

Prompt (instruction for AI agent)
Use $ams2026

Compare the HOMO-LUMO gap and frontier orbitals of ethylene with two ADF
settings.

Build ethylene from SMILES C=C. Optimize the geometry once with a modest ADF
GGA setup. Then run two ADF single-point calculations on the optimized
geometry: one with the same GGA setup and one with a hybrid functional.

For both single-point calculations, extract HOMO energy, LUMO energy, HOMO-LUMO
gap, and the energies of the nearest few occupied and virtual orbitals. Keep
units clear and report orbital energies in eV.

In the report, include a table comparing the two methods, and plot the frontier
orbital energy levels as a small energy-level diagram.

Include a picture of the optimized ethylene geometry. Include HOMO and LUMO
orbital isosurface pictures generated with amsreport. 
01-run.py
#!/usr/bin/env amspython
from __future__ import annotations

from scm.base import ChemicalSystem, InputParser
from scm.plams import AMSJob, Settings, finish, init


def adf_settings(task: str, functional_kind: str, functional: str) -> Settings:
    settings = Settings()
    settings.input.ams.Task = task
    settings.input.adf.Basis.Type = "DZP"
    settings.input.adf.Basis.Core = "None"
    settings.input.adf.NumericalQuality = "Normal"
    settings.input.adf.SCF.Iterations = 100
    if functional_kind == "GGA":
        settings.input.adf.XC.GGA = functional
    elif functional_kind == "Hybrid":
        settings.input.adf.XC.Hybrid = functional
    else:
        raise ValueError(f"Unsupported functional kind: {functional_kind}")
    return settings


def validate_job(job: AMSJob) -> None:
    InputParser().to_dict("ams", job.get_input())


def main() -> None:
    init(folder="01-run_workdir")

    initial_system = ChemicalSystem.from_smiles("C=C")
    optimization = AMSJob(
        molecule=initial_system,
        settings=adf_settings("GeometryOptimization", "GGA", "PBE"),
        name="ethylene_opt_pbe_dzp",
    )
    validate_job(optimization)
    optimization.run()
    if not optimization.ok():
        raise RuntimeError("PBE/DZP geometry optimization failed")

    optimized_system = optimization.results.get_main_system()
    single_points = (
        ("ethylene_sp_pbe_dzp", "GGA", "PBE"),
        ("ethylene_sp_b3lyp_dzp", "Hybrid", "B3LYP"),
    )
    for name, functional_kind, functional in single_points:
        job = AMSJob(
            molecule=optimized_system,
            settings=adf_settings("SinglePoint", functional_kind, functional),
            name=name,
        )
        validate_job(job)
        job.run()
        if not job.ok():
            raise RuntimeError(f"Single-point calculation failed: {name}")

    finish()


if __name__ == "__main__":
    main()
report.py
#!/usr/bin/env amspython
from __future__ import annotations

import os
import shutil
import subprocess
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scm.plams import AMSJob, view


ROOT = Path(__file__).resolve().parent
FIGURES = ROOT / "figures"
TABLES = ROOT / "tables"


def latest_workdir() -> Path:
    candidates = [path for path in ROOT.glob("01-run_workdir*") if path.is_dir()]
    if not candidates:
        raise FileNotFoundError("No 01-run_workdir directory was found")
    return max(candidates, key=lambda path: path.stat().st_mtime)


def frontier_rows(job: AMSJob, method: str, count: int = 3) -> list[dict[str, object]]:
    energies = np.asarray(job.results.get_orbital_energies(unit="eV"))[0]
    occupations = np.asarray(job.results.get_orbital_occupations())[0]
    occupied = np.flatnonzero(occupations > 1.0e-6)
    virtual = np.flatnonzero(occupations <= 1.0e-6)
    if len(occupied) < count or len(virtual) < count:
        raise RuntimeError(f"Too few orbitals were returned for {method}")
    homo_index = int(occupied[-1])
    lumo_index = int(virtual[0])
    selected = list(occupied[-count:]) + list(virtual[:count])
    rows: list[dict[str, object]] = []
    for index in selected:
        if index <= homo_index:
            offset = homo_index - int(index)
            label = "HOMO" if offset == 0 else f"HOMO-{offset}"
        else:
            offset = int(index) - lumo_index
            label = "LUMO" if offset == 0 else f"LUMO+{offset}"
        rows.append(
            {
                "Method": method,
                "Orbital": label,
                "Energy (eV)": float(energies[index]),
                "Occupation": float(occupations[index]),
            }
        )
    return rows


def make_energy_level_plot(orbital_table: pd.DataFrame) -> None:
    fig, ax = plt.subplots(figsize=(5.2, 5.0))
    methods = list(orbital_table["Method"].drop_duplicates())
    for x, method in enumerate(methods):
        subset = orbital_table[orbital_table["Method"] == method]
        for _, row in subset.iterrows():
            label = str(row["Orbital"])
            energy = float(row["Energy (eV)"])
            frontier = label in {"HOMO", "LUMO"}
            color = "#2166ac" if float(row["Occupation"]) > 0 else "#b2182b"
            linewidth = 3.0 if frontier else 1.5
            ax.hlines(energy, x - 0.23, x + 0.23, color=color, linewidth=linewidth)
            ax.text(x + 0.27, energy, label, va="center", fontsize=8)
    ax.set_xlim(-0.55, len(methods) - 0.25)
    ax.set_xticks(range(len(methods)), methods)
    ax.set_ylabel("Orbital energy (eV)")
    ax.set_title("Frontier orbital energy levels")
    ax.grid(axis="y", color="#dddddd", linewidth=0.6)
    fig.tight_layout()
    fig.savefig(FIGURES / "frontier_energy_levels.png", dpi=200)
    plt.close(fig)


def make_amsreport_orbital(engine_rkf: Path, method_slug: str, orbital: str) -> Path:
    output_dir = FIGURES / f"amsreport_{method_slug}_{orbital.lower()}"
    if output_dir.exists():
        shutil.rmtree(output_dir)
    output_dir.mkdir(parents=True)
    command = [
        os.environ["AMSBIN"] + "/amsreport",
        str(engine_rkf),
        orbital,
        "-o",
        "report.html",
        "-v",
        "-scmgeometry 500x400",
        "-v",
        "-grid Fine",
        "-v",
        "-antialias",
        "-v",
        "-bgcolor #ffffff",
        "-v",
        "-viewplane {1 2 5}",
    ]
    subprocess.run(command, cwd=output_dir, check=True, capture_output=True, text=True)
    images = sorted(output_dir.rglob("*.jpg")) + sorted(output_dir.rglob("*.png"))
    if not images:
        raise RuntimeError(f"amsreport did not create an image for {method_slug} {orbital}")
    return images[0].relative_to(ROOT)


def main() -> None:
    FIGURES.mkdir(exist_ok=True)
    TABLES.mkdir(exist_ok=True)
    workdir = latest_workdir()

    # Geometry optimization: establishes the one structure used by both single points.
    optimization = AMSJob.load_external(str(workdir / "ethylene_opt_pbe_dzp"))
    # PBE single point: supplies the GGA orbital energies and isosurfaces.
    pbe = AMSJob.load_external(str(workdir / "ethylene_sp_pbe_dzp"))
    # B3LYP single point: supplies the hybrid orbital energies and isosurfaces.
    b3lyp = AMSJob.load_external(str(workdir / "ethylene_sp_b3lyp_dzp"))

    jobs = {"PBE/DZP": pbe, "B3LYP/DZP": b3lyp}
    summary_rows: list[dict[str, object]] = []
    orbital_rows: list[dict[str, object]] = []
    for method, job in jobs.items():
        homo = float(job.results.get_homo_energies(unit="eV")[0])
        lumo = float(job.results.get_lumo_energies(unit="eV")[0])
        gap = float(job.results.get_smallest_homo_lumo_gap(unit="eV"))
        summary_rows.append(
            {"Method": method, "HOMO (eV)": homo, "LUMO (eV)": lumo, "Gap (eV)": gap}
        )
        orbital_rows.extend(frontier_rows(job, method))

    summary = pd.DataFrame(summary_rows)
    orbitals = pd.DataFrame(orbital_rows)
    summary.to_csv(TABLES / "frontier_summary.csv", index=False)
    orbitals.to_csv(TABLES / "frontier_orbitals.csv", index=False)
    make_energy_level_plot(orbitals)

    optimized_system = optimization.results.get_main_system()
    view(
        optimized_system,
        guess_bonds=len(optimized_system.bonds) == 0,
        direction="along_pca3",
        width=600,
        height=450,
        picture_path=str(FIGURES / "optimized_ethylene.png"),
    )

    orbital_images: dict[tuple[str, str], Path] = {}
    for method_slug, job in (("pbe", pbe), ("b3lyp", b3lyp)):
        engine_rkf = Path(job.results.rkfpath(file="engine")).resolve()
        for orbital in ("HOMO", "LUMO"):
            orbital_images[(method_slug, orbital)] = make_amsreport_orbital(
                engine_rkf, method_slug, orbital
            )

    carbon_indices = [i for i, atom in enumerate(optimized_system.atoms) if atom.symbol == "C"]
    cc_distance = optimized_system.get_distance(carbon_indices[0], carbon_indices[1])
    gap_change = float(summary.loc[summary["Method"] == "B3LYP/DZP", "Gap (eV)"].iloc[0]) - float(
        summary.loc[summary["Method"] == "PBE/DZP", "Gap (eV)"].iloc[0]
    )

    lines = [
        "# Ethylene frontier orbitals with PBE and B3LYP",
        "",
        "Ethylene was built from the SMILES string `C=C` and optimized once with ADF at PBE/DZP. "
        "Two single-point calculations, PBE/DZP and B3LYP/DZP, then used that same geometry. "
        "Both calculations used an all-electron DZP basis and Normal numerical quality. Orbital energies are Kohn-Sham eigenvalues in eV.",
        "",
        "## Optimized geometry",
        "",
        f"The optimized C=C distance is {cc_distance:.4f} Å.",
        "",
        "![Optimized ethylene geometry](figures/optimized_ethylene.png)",
        "",
        "## Frontier orbital energies",
        "",
        summary.to_markdown(index=False, floatfmt=".4f"),
        "",
        "The nearest three occupied and three virtual orbitals are:",
        "",
        orbitals.to_markdown(index=False, floatfmt=("", "", ".4f", ".1f")),
        "",
        "![Frontier orbital energy-level diagram](figures/frontier_energy_levels.png)",
        "",
        "## HOMO and LUMO isosurfaces",
        "",
        "The four orbital pictures below were generated by `amsreport` from the corresponding ADF engine result files. Contrasting colors mark opposite orbital phases.",
        "",
        "| Method | HOMO | LUMO |",
        "|---|---|---|",
        f"| PBE/DZP | ![PBE HOMO]({orbital_images[('pbe', 'HOMO')].as_posix()}) | ![PBE LUMO]({orbital_images[('pbe', 'LUMO')].as_posix()}) |",
        f"| B3LYP/DZP | ![B3LYP HOMO]({orbital_images[('b3lyp', 'HOMO')].as_posix()}) | ![B3LYP LUMO]({orbital_images[('b3lyp', 'LUMO')].as_posix()}) |",
        "",
        "## Conclusion",
        "",
        f"At this fixed PBE geometry, replacing PBE with B3LYP changes the Kohn-Sham HOMO-LUMO gap by {gap_change:+.4f} eV. "
        "The HOMO and LUMO isosurfaces allow the comparison to distinguish an energy shift from a qualitative change in orbital shape. "
        "These Kohn-Sham gaps are method-dependent orbital-energy differences, not optical excitation energies.",
        "",
        "## Calculation inputs",
        "",
    ]
    for title, job in (
        ("PBE/DZP geometry optimization", optimization),
        ("PBE/DZP single point", pbe),
        ("B3LYP/DZP single point", b3lyp),
    ):
        lines.extend([f"### {title}", "", "```ams", job.get_input().rstrip(), "```", ""])
    (ROOT / "report.md").write_text("\n".join(lines), encoding="utf-8")


if __name__ == "__main__":
    main()
Original Markdown report
# Ethylene frontier orbitals with PBE and B3LYP

Ethylene was built from the SMILES string `C=C` and optimized once with ADF at PBE/DZP. Two single-point calculations, PBE/DZP and B3LYP/DZP, then used that same geometry. Both calculations used an all-electron DZP basis and Normal numerical quality. Orbital energies are Kohn-Sham eigenvalues in eV.

## Optimized geometry

The optimized C=C distance is 1.3320 Å.

![Optimized ethylene geometry](figures/optimized_ethylene.png)

## Frontier orbital energies

| Method    |   HOMO (eV) |   LUMO (eV) |   Gap (eV) |
|:----------|------------:|------------:|-----------:|
| PBE/DZP   |     -7.0061 |     -1.1879 |     5.8182 |
| B3LYP/DZP |     -7.7749 |     -0.2106 |     7.5642 |

The nearest three occupied and three virtual orbitals are:

| Method    | Orbital   |   Energy (eV) |   Occupation |
|:----------|:----------|--------------:|-------------:|
| PBE/DZP   | HOMO-2    |      -10.4784 |          2.0 |
| PBE/DZP   | HOMO-1    |       -8.7810 |          2.0 |
| PBE/DZP   | HOMO      |       -7.0061 |          2.0 |
| PBE/DZP   | LUMO      |       -1.1879 |          0.0 |
| PBE/DZP   | LUMO+1    |        0.8367 |          0.0 |
| PBE/DZP   | LUMO+2    |        1.9669 |          0.0 |
| B3LYP/DZP | HOMO-2    |      -11.7766 |          2.0 |
| B3LYP/DZP | HOMO-1    |       -9.9698 |          2.0 |
| B3LYP/DZP | HOMO      |       -7.7749 |          2.0 |
| B3LYP/DZP | LUMO      |       -0.2106 |          0.0 |
| B3LYP/DZP | LUMO+1    |        1.3945 |          0.0 |
| B3LYP/DZP | LUMO+2    |        2.5806 |          0.0 |

![Frontier orbital energy-level diagram](figures/frontier_energy_levels.png)

## HOMO and LUMO isosurfaces

The four orbital pictures below were generated by `amsreport` from the corresponding ADF engine result files. Contrasting colors mark opposite orbital phases.

| Method | HOMO | LUMO |
|---|---|---|
| PBE/DZP | ![PBE HOMO](figures/amsreport_pbe_homo/report.jpgs/0.jpg) | ![PBE LUMO](figures/amsreport_pbe_lumo/report.jpgs/0.jpg) |
| B3LYP/DZP | ![B3LYP HOMO](figures/amsreport_b3lyp_homo/report.jpgs/0.jpg) | ![B3LYP LUMO](figures/amsreport_b3lyp_lumo/report.jpgs/0.jpg) |

## Conclusion

At this fixed PBE geometry, replacing PBE with B3LYP changes the Kohn-Sham HOMO-LUMO gap by +1.7460 eV. The HOMO and LUMO isosurfaces allow the comparison to distinguish an energy shift from a qualitative change in orbital shape. These Kohn-Sham gaps are method-dependent orbital-energy differences, not optical excitation energies.

## Calculation inputs

### PBE/DZP geometry optimization

```ams
Task GeometryOptimization

System
  Atoms
              C       0.6644850805       0.0279879686      -0.0236852009
              C      -0.6644849893      -0.0279879318       0.0236851499
              H       1.2534332310      -0.8786144292       0.0702991539
              H       1.1670384263       0.9805640030      -0.1565753856
              H      -1.2534332920       0.8786143859      -0.0702991171
              H      -1.1670384566      -0.9805639965       0.1565753997
  End
  BondOrders
     1 2 2.0
     1 3 1.0
     1 4 1.0
     2 5 1.0
     2 6 1.0
  End
End

Engine adf
  Basis
    Core None
    Type DZP
  End
  NumericalQuality Normal
  SCF
    Iterations 100
  End
  XC
    GGA PBE
  End
EndEngine
```

### PBE/DZP single point

```ams
Task SinglePoint

System
  Atoms
              C       0.6649813115       0.0278988522      -0.0242986367
              C      -0.6649810155      -0.0278982997       0.0243008447
              H       1.2789279657      -0.8699226278       0.0686049752
              H       1.1932069166       0.9740820859      -0.1564006741
              H      -1.2789292580       0.8699198605      -0.0686220517
              H      -1.1932059203      -0.9740798711       0.1564155425
  End
  BondOrders
     1 2 2.0
     1 3 1.0
     1 4 1.0
     2 5 1.0
     2 6 1.0
  End
End

Engine adf
  Basis
    Core None
    Type DZP
  End
  NumericalQuality Normal
  SCF
    Iterations 100
  End
  XC
    GGA PBE
  End
EndEngine
```

### B3LYP/DZP single point

```ams
Task SinglePoint

System
  Atoms
              C       0.6649813115       0.0278988522      -0.0242986367
              C      -0.6649810155      -0.0278982997       0.0243008447
              H       1.2789279657      -0.8699226278       0.0686049752
              H       1.1932069166       0.9740820859      -0.1564006741
              H      -1.2789292580       0.8699198605      -0.0686220517
              H      -1.1932059203      -0.9740798711       0.1564155425
  End
  BondOrders
     1 2 2.0
     1 3 1.0
     1 4 1.0
     2 5 1.0
     2 6 1.0
  End
End

Engine adf
  Basis
    Core None
    Type DZP
  End
  NumericalQuality Normal
  SCF
    Iterations 100
  End
  XC
    Hybrid B3LYP
  End
EndEngine
```

Note on AI-generated content

This page was generated by a Python script. That Python script was AI-generated.

All numbers, figures, and tables are extracted or postprocessed from actual AMS calculations, and can be transparently regenerated from the provided Python scripts.

Any scientific reasoning or citations was written by AI. This page is the actual one-shot output from using the ams2026 skill with an AI coding agent.