Ethylene ADF Frontier Orbital Comparison

../_images/frontier_energy_levels_0ea2774f.png

PBE and B3LYP frontier-orbital energy levels.

Requires: AMS2026 or later

Related documentation

Ethylene was built from SMILES C=C. The geometry was optimized once with a modest ADF GGA setup, then two ADF single-point calculations were run on the optimized geometry: PBE/DZP and B3LYP/DZP. Orbital energies are reported in eV.

Job provenance

Role

Method

Job directory

Justification

Geometry optimization

ADF/PBE/DZP

01-run_workdir/ethylene_opt_pbe_dzp

Optimized the ethylene geometry once from SMILES C=C.

GGA single point

ADF/PBE/DZP

01-run_workdir/ethylene_sp_pbe_dzp

Computed frontier orbital energies on the optimized geometry with the GGA setup.

Hybrid single point

ADF/B3LYP/DZP

01-run_workdir/ethylene_sp_b3lyp_dzp

Computed frontier orbital energies on the same optimized geometry with a hybrid functional.

Optimized geometry

The optimized C=C distance is 1.332 angstrom. The mean C-H distance is 1.092 angstrom.

Optimized ethylene geometry

HOMO-LUMO comparison

Method

HOMO (eV)

LUMO (eV)

Gap (eV)

HOMO index

LUMO index

ADF/PBE/DZP

-7.036

-1.235

5.801

6

7

ADF/B3LYP/DZP

-7.853

-0.292

7.561

6

7

Frontier orbital energy windows

ADF/PBE/DZP

Orbital

Index

Occupation

Energy (eV)

HOMO-3

3

2.000

-11.754

HOMO-2

4

2.000

-10.519

HOMO-1

5

2.000

-8.797

HOMO

6

2.000

-7.036

LUMO

7

0.000

-1.235

LUMO+1

8

0.000

0.886

LUMO+2

9

0.000

2.018

LUMO+3

10

0.000

2.059

ADF/B3LYP/DZP

Orbital

Index

Occupation

Energy (eV)

HOMO-3

3

2.000

-13.120

HOMO-2

4

2.000

-11.871

HOMO-1

5

2.000

-10.026

HOMO

6

2.000

-7.853

LUMO

7

0.000

-0.292

LUMO+1

8

0.000

1.473

LUMO+2

9

0.000

2.696

LUMO+3

10

0.000

2.778

Energy-level diagram

Frontier orbital energy levels

AMSreport orbital isosurfaces

These images were generated by amsreport from each single-point adf.rkf file.

Method

HOMO

LUMO

ADF/PBE/DZP

PBE HOMO

PBE LUMO

ADF/B3LYP/DZP

B3LYP HOMO

B3LYP LUMO

Calculation inputs

Geometry optimization: ADF/PBE/DZP

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
    Type DZP
  End
  NumericalQuality Basic
  XC
    GGA PBE
  End
EndEngine

GGA single point: ADF/PBE/DZP

Properties
  OrbitalsInfo yes
End

Task SinglePoint

System
  Atoms
              C       0.6651665049       0.0281045107      -0.0235971144
              C      -0.6651713207      -0.0281053452       0.0236173947
              H       1.2802120569      -0.8698054143       0.0685226284
              H       1.1943503882       0.9738532541      -0.1567756217
              H      -1.2801922123       0.8698550678      -0.0682160798
              H      -1.1943654169      -0.9739020730       0.1564487929
  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
    Type DZP
  End
  NumericalQuality Basic
  XC
    GGA PBE
  End
EndEngine

Hybrid single point: ADF/B3LYP/DZP

Properties
  OrbitalsInfo yes
End

Task SinglePoint

System
  Atoms
              C       0.6651665049       0.0281045107      -0.0235971144
              C      -0.6651713207      -0.0281053452       0.0236173947
              H       1.2802120569      -0.8698054143       0.0685226284
              H       1.1943503882       0.9738532541      -0.1567756217
              H      -1.2801922123       0.8698550678      -0.0682160798
              H      -1.1943654169      -0.9739020730       0.1564487929
  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
    Type DZP
  End
  NumericalQuality Basic
  XC
    Hybrid B3LYP
  End
EndEngine

Conclusion

With this modest DZP setup, the B3LYP single point increases the HOMO-LUMO gap from 5.801 eV for PBE to 7.561 eV. The increase is mainly from a lower HOMO and a higher LUMO relative to the PBE values.

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 pathlib import Path

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

try:
    from scm.input_classes import AMS
except ImportError:
    from scm.input_classes.drivers import AMS


WORKDIR = "01-run_workdir"


def adf_settings(task: str, xc_type: str, xc_name: str) -> Settings:
    settings = Settings()
    settings.input.AMS.Task = task
    if task == "SinglePoint":
        settings.input.AMS.Properties.OrbitalsInfo = "Yes"
    settings.input.ADF.Basis.Type = "DZP"
    settings.input.ADF.NumericalQuality = "Basic"
    setattr(settings.input.ADF.XC, xc_type, xc_name)
    AMS.from_settings(settings)
    return settings


def run_job(name: str, system: ChemicalSystem, settings: Settings) -> AMSJob:
    job = AMSJob(name=name, molecule=system, settings=settings)
    print(f"\n--- {name} input ---")
    print(job.get_input())
    print(f"--- end {name} input ---\n")
    result = job.run()
    if not result.ok():
        raise RuntimeError(f"AMS job failed: {name}")
    return job


def main() -> None:
    init(folder=WORKDIR)
    try:
        outdir = Path("figures")
        outdir.mkdir(exist_ok=True)

        ethylene = ChemicalSystem.from_smiles("C=C")
        opt_settings = adf_settings("GeometryOptimization", "GGA", "PBE")
        opt_job = run_job("ethylene_opt_pbe_dzp", ethylene, opt_settings)

        optimized = opt_job.results.get_main_system()
        view(
            optimized,
            guess_bonds=True,
            direction="along_pca3",
            width=500,
            height=360,
            picture_path=str(outdir / "optimized_ethylene.png"),
        )

        gga_sp = run_job(
            "ethylene_sp_pbe_dzp",
            optimized,
            adf_settings("SinglePoint", "GGA", "PBE"),
        )
        hybrid_sp = run_job(
            "ethylene_sp_b3lyp_dzp",
            optimized,
            adf_settings("SinglePoint", "Hybrid", "B3LYP"),
        )

        print("Finished jobs:")
        for job in (opt_job, gga_sp, hybrid_sp):
            print(f"{job.name}: {job.path}")
    finally:
        finish()


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

from dataclasses import dataclass
import os
from pathlib import Path
import shutil
import subprocess

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


@dataclass(frozen=True)
class JobRef:
    label: str
    method: str
    path: Path
    justification: str


def latest_workdir(prefix: str) -> Path:
    candidates = [p for p in Path(".").glob(f"{prefix}*") if p.is_dir()]
    if not candidates:
        raise FileNotFoundError(f"No PLAMS workdir matching {prefix}*")
    return max(candidates, key=lambda p: p.stat().st_mtime)


def frontier_window(job: AMSJob, n_each_side: int = 3) -> tuple[pd.DataFrame, dict[str, float]]:
    energies = np.asarray(job.results.get_orbital_energies(unit="eV"))[0]
    occupations = np.asarray(job.results.get_orbital_occupations())[0]
    occupied = np.where(occupations > 1.0e-6)[0]
    virtual = np.where(occupations <= 1.0e-6)[0]
    if len(occupied) == 0 or len(virtual) == 0:
        raise RuntimeError(f"Could not identify occupied and virtual orbitals for {job.name}")

    homo_idx = int(occupied[-1])
    lumo_idx = int(virtual[virtual > homo_idx][0])
    start = max(0, homo_idx - n_each_side)
    stop = min(len(energies), lumo_idx + n_each_side + 1)

    rows: list[dict[str, object]] = []
    for idx in range(start, stop):
        label = f"HOMO{idx - homo_idx:+d}" if idx <= homo_idx else f"LUMO{idx - lumo_idx:+d}"
        label = label.replace("+0", "").replace("-0", "")
        rows.append(
            {
                "Orbital": label,
                "Index": idx + 1,
                "Occupation": float(occupations[idx]),
                "Energy (eV)": float(energies[idx]),
            }
        )

    summary = {
        "HOMO (eV)": float(job.results.get_homo_energies(unit="eV")[0]),
        "LUMO (eV)": float(job.results.get_lumo_energies(unit="eV")[0]),
        "Gap (eV)": float(job.results.get_smallest_homo_lumo_gap(unit="eV")),
        "HOMO index": homo_idx + 1,
        "LUMO index": lumo_idx + 1,
    }
    return pd.DataFrame(rows), summary


def plot_levels(windows: dict[str, pd.DataFrame], output: Path) -> None:
    fig, ax = plt.subplots(figsize=(5.8, 3.6))
    x_positions = {"PBE/DZP": 0.0, "B3LYP/DZP": 1.0}
    colors = {"occupied": "#1f77b4", "virtual": "#d62728"}

    for method, df in windows.items():
        x = x_positions[method]
        for _, row in df.iterrows():
            occ = float(row["Occupation"])
            energy = float(row["Energy (eV)"])
            color = colors["occupied" if occ > 1.0e-6 else "virtual"]
            ax.hlines(energy, x - 0.22, x + 0.22, color=color, linewidth=2)
            if row["Orbital"] in {"HOMO", "LUMO"}:
                ax.text(x + 0.26, energy, str(row["Orbital"]), va="center", fontsize=8)

    ax.set_xticks(list(x_positions.values()), list(x_positions.keys()))
    ax.set_ylabel("Orbital energy (eV)")
    ax.set_xlim(-0.55, 1.65)
    ax.grid(axis="y", color="#dddddd", linewidth=0.8)
    ax.spines[["top", "right"]].set_visible(False)
    ax.plot([], [], color=colors["occupied"], label="Occupied")
    ax.plot([], [], color=colors["virtual"], label="Virtual")
    ax.legend(frameon=False, fontsize=8, loc="lower right")
    fig.tight_layout()
    fig.savefig(output, dpi=180)
    plt.close(fig)


def generate_amsreport_image(result_file: Path, orbital: str, html_file: Path, image_file: Path) -> None:
    amsbin = Path(os.environ["AMSBIN"])
    command = [
        "xvfb-run",
        "-a",
        str(amsbin / "amsreport"),
        str(result_file),
        orbital,
        "-o",
        str(html_file),
        "-v",
        "-grid Fine",
        "-v",
        "-antialias",
        "-v",
        "-bgcolor #ffffff",
        "-v",
        "-scmgeometry 500x500",
    ]
    subprocess.run(command, check=True)
    generated = html_file.with_suffix(".jpgs") / "0.jpg"
    if not generated.exists() or generated.stat().st_size == 0:
        raise RuntimeError(f"AMSreport did not create a usable image for {orbital} from {result_file}")
    shutil.copyfile(generated, image_file)


def fmt_df(df: pd.DataFrame, digits: int = 3) -> str:
    return df.to_markdown(index=False, floatfmt=f".{digits}f")


def main() -> None:
    workdir = latest_workdir("01-run_workdir")
    refs = [
        JobRef(
            "Geometry optimization",
            "ADF/PBE/DZP",
            workdir / "ethylene_opt_pbe_dzp",
            "Optimized the ethylene geometry once from SMILES C=C.",
        ),
        JobRef(
            "GGA single point",
            "ADF/PBE/DZP",
            workdir / "ethylene_sp_pbe_dzp",
            "Computed frontier orbital energies on the optimized geometry with the GGA setup.",
        ),
        JobRef(
            "Hybrid single point",
            "ADF/B3LYP/DZP",
            workdir / "ethylene_sp_b3lyp_dzp",
            "Computed frontier orbital energies on the same optimized geometry with a hybrid functional.",
        ),
    ]
    jobs = {ref.label: AMSJob.load_external(str(ref.path)) for ref in refs}

    windows: dict[str, pd.DataFrame] = {}
    summary_rows: list[dict[str, object]] = []
    for ref in refs[1:]:
        df, summary = frontier_window(jobs[ref.label])
        method_key = "PBE/DZP" if "PBE" in ref.method else "B3LYP/DZP"
        windows[method_key] = df
        summary_rows.append({"Method": ref.method, **summary})

    figures = Path("figures")
    figures.mkdir(exist_ok=True)
    plot_levels(windows, figures / "frontier_energy_levels.png")

    amsreport_dir = Path("amsreport_images")
    amsreport_dir.mkdir(exist_ok=True)
    generate_amsreport_image(
        refs[1].path / "adf.rkf",
        "HOMO",
        amsreport_dir / "pbe_homo.html",
        figures / "pbe_homo_amsreport.jpg",
    )
    generate_amsreport_image(
        refs[1].path / "adf.rkf",
        "LUMO",
        amsreport_dir / "pbe_lumo.html",
        figures / "pbe_lumo_amsreport.jpg",
    )
    generate_amsreport_image(
        refs[2].path / "adf.rkf",
        "HOMO",
        amsreport_dir / "b3lyp_homo.html",
        figures / "b3lyp_homo_amsreport.jpg",
    )
    generate_amsreport_image(
        refs[2].path / "adf.rkf",
        "LUMO",
        amsreport_dir / "b3lyp_lumo.html",
        figures / "b3lyp_lumo_amsreport.jpg",
    )

    opt_system = jobs["Geometry optimization"].results.get_main_system()
    cc_distance = opt_system.get_distance(0, 1, unit="angstrom")
    ch_distances = [opt_system.get_distance(0, i, unit="angstrom") for i in (2, 3)]
    ch_distances += [opt_system.get_distance(1, i, unit="angstrom") for i in (4, 5)]

    summary_df = pd.DataFrame(summary_rows)
    report = [
        "# Ethylene ADF Frontier Orbital Comparison",
        "",
        "Ethylene was built from SMILES `C=C`. The geometry was optimized once with a modest ADF GGA setup, then two ADF single-point calculations were run on the optimized geometry: PBE/DZP and B3LYP/DZP. Orbital energies are reported in eV.",
        "",
        "## Job provenance",
        "",
        "| Role | Method | Job directory | Justification |",
        "|---|---|---|---|",
    ]
    for ref in refs:
        report.append(f"| {ref.label} | {ref.method} | `{ref.path}` | {ref.justification} |")

    report += [
        "",
        "## Optimized geometry",
        "",
        f"The optimized C=C distance is {cc_distance:.3f} angstrom. The mean C-H distance is {np.mean(ch_distances):.3f} angstrom.",
        "",
        "![Optimized ethylene geometry](figures/optimized_ethylene.png)",
        "",
        "## HOMO-LUMO comparison",
        "",
        fmt_df(summary_df[["Method", "HOMO (eV)", "LUMO (eV)", "Gap (eV)", "HOMO index", "LUMO index"]]),
        "",
        "## Frontier orbital energy windows",
        "",
        "### ADF/PBE/DZP",
        "",
        fmt_df(windows["PBE/DZP"]),
        "",
        "### ADF/B3LYP/DZP",
        "",
        fmt_df(windows["B3LYP/DZP"]),
        "",
        "## Energy-level diagram",
        "",
        "![Frontier orbital energy levels](figures/frontier_energy_levels.png)",
        "",
        "## AMSreport orbital isosurfaces",
        "",
        "These images were generated by `amsreport` from each single-point `adf.rkf` file.",
        "",
        "| Method | HOMO | LUMO |",
        "|---|---|---|",
        "| ADF/PBE/DZP | ![PBE HOMO](figures/pbe_homo_amsreport.jpg) | ![PBE LUMO](figures/pbe_lumo_amsreport.jpg) |",
        "| ADF/B3LYP/DZP | ![B3LYP HOMO](figures/b3lyp_homo_amsreport.jpg) | ![B3LYP LUMO](figures/b3lyp_lumo_amsreport.jpg) |",
        "",
        "## Calculation inputs",
        "",
    ]
    for ref in refs:
        report += [
            f"### {ref.label}: {ref.method}",
            "",
            "```ams",
            jobs[ref.label].get_input(),
            "```",
            "",
        ]

    report += [
        "## Conclusion",
        "",
        f"With this modest DZP setup, the B3LYP single point increases the HOMO-LUMO gap from {summary_rows[0]['Gap (eV)']:.3f} eV for PBE to {summary_rows[1]['Gap (eV)']:.3f} eV. The increase is mainly from a lower HOMO and a higher LUMO relative to the PBE values.",
        "",
    ]
    Path("report.md").write_text("\n".join(report), encoding="utf-8")
    print(Path("report.md").resolve())


if __name__ == "__main__":
    main()
Original Markdown report
# Ethylene ADF Frontier Orbital Comparison

Ethylene was built from SMILES `C=C`. The geometry was optimized once with a modest ADF GGA setup, then two ADF single-point calculations were run on the optimized geometry: PBE/DZP and B3LYP/DZP. Orbital energies are reported in eV.

## Job provenance

| Role | Method | Job directory | Justification |
|---|---|---|---|
| Geometry optimization | ADF/PBE/DZP | `01-run_workdir/ethylene_opt_pbe_dzp` | Optimized the ethylene geometry once from SMILES C=C. |
| GGA single point | ADF/PBE/DZP | `01-run_workdir/ethylene_sp_pbe_dzp` | Computed frontier orbital energies on the optimized geometry with the GGA setup. |
| Hybrid single point | ADF/B3LYP/DZP | `01-run_workdir/ethylene_sp_b3lyp_dzp` | Computed frontier orbital energies on the same optimized geometry with a hybrid functional. |

## Optimized geometry

The optimized C=C distance is 1.332 angstrom. The mean C-H distance is 1.092 angstrom.

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

## HOMO-LUMO comparison

| Method        |   HOMO (eV) |   LUMO (eV) |   Gap (eV) |   HOMO index |   LUMO index |
|:--------------|------------:|------------:|-----------:|-------------:|-------------:|
| ADF/PBE/DZP   |      -7.036 |      -1.235 |      5.801 |            6 |            7 |
| ADF/B3LYP/DZP |      -7.853 |      -0.292 |      7.561 |            6 |            7 |

## Frontier orbital energy windows

### ADF/PBE/DZP

| Orbital   |   Index |   Occupation |   Energy (eV) |
|:----------|--------:|-------------:|--------------:|
| HOMO-3    |       3 |        2.000 |       -11.754 |
| HOMO-2    |       4 |        2.000 |       -10.519 |
| HOMO-1    |       5 |        2.000 |        -8.797 |
| HOMO      |       6 |        2.000 |        -7.036 |
| LUMO      |       7 |        0.000 |        -1.235 |
| LUMO+1    |       8 |        0.000 |         0.886 |
| LUMO+2    |       9 |        0.000 |         2.018 |
| LUMO+3    |      10 |        0.000 |         2.059 |

### ADF/B3LYP/DZP

| Orbital   |   Index |   Occupation |   Energy (eV) |
|:----------|--------:|-------------:|--------------:|
| HOMO-3    |       3 |        2.000 |       -13.120 |
| HOMO-2    |       4 |        2.000 |       -11.871 |
| HOMO-1    |       5 |        2.000 |       -10.026 |
| HOMO      |       6 |        2.000 |        -7.853 |
| LUMO      |       7 |        0.000 |        -0.292 |
| LUMO+1    |       8 |        0.000 |         1.473 |
| LUMO+2    |       9 |        0.000 |         2.696 |
| LUMO+3    |      10 |        0.000 |         2.778 |

## Energy-level diagram

![Frontier orbital energy levels](figures/frontier_energy_levels.png)

## AMSreport orbital isosurfaces

These images were generated by `amsreport` from each single-point `adf.rkf` file.

| Method | HOMO | LUMO |
|---|---|---|
| ADF/PBE/DZP | ![PBE HOMO](figures/pbe_homo_amsreport.jpg) | ![PBE LUMO](figures/pbe_lumo_amsreport.jpg) |
| ADF/B3LYP/DZP | ![B3LYP HOMO](figures/b3lyp_homo_amsreport.jpg) | ![B3LYP LUMO](figures/b3lyp_lumo_amsreport.jpg) |

## Calculation inputs

### Geometry optimization: ADF/PBE/DZP

```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
    Type DZP
  End
  NumericalQuality Basic
  XC
    GGA PBE
  End
EndEngine


```

### GGA single point: ADF/PBE/DZP

```ams
Properties
  OrbitalsInfo yes
End

Task SinglePoint

System
  Atoms
              C       0.6651665049       0.0281045107      -0.0235971144
              C      -0.6651713207      -0.0281053452       0.0236173947
              H       1.2802120569      -0.8698054143       0.0685226284
              H       1.1943503882       0.9738532541      -0.1567756217
              H      -1.2801922123       0.8698550678      -0.0682160798
              H      -1.1943654169      -0.9739020730       0.1564487929
  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
    Type DZP
  End
  NumericalQuality Basic
  XC
    GGA PBE
  End
EndEngine


```

### Hybrid single point: ADF/B3LYP/DZP

```ams
Properties
  OrbitalsInfo yes
End

Task SinglePoint

System
  Atoms
              C       0.6651665049       0.0281045107      -0.0235971144
              C      -0.6651713207      -0.0281053452       0.0236173947
              H       1.2802120569      -0.8698054143       0.0685226284
              H       1.1943503882       0.9738532541      -0.1567756217
              H      -1.2801922123       0.8698550678      -0.0682160798
              H      -1.1943654169      -0.9739020730       0.1564487929
  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
    Type DZP
  End
  NumericalQuality Basic
  XC
    Hybrid B3LYP
  End
EndEngine


```

## Conclusion

With this modest DZP setup, the B3LYP single point increases the HOMO-LUMO gap from 5.801 eV for PBE to 7.561 eV. The increase is mainly from a lower HOMO and a higher LUMO relative to the PBE values.