Biphenyl DFTB Torsional PES Scan

This report shows a scan of the central torsion in biphenyl with DFTB and summarizes the resulting energy profile, representative structures, and reusable Python workflow.

../_images/biphenyl_torsion_profile_e7db2ed9.png

DFTB torsional potential-energy profile of biphenyl.

Requires: AMS2026 or later

Related documentation

Setup

Biphenyl was built from SMILES c1ccccc1-c2ccccc2 with ChemicalSystem.from_smiles. The central inter-ring C-C bond was identified by inspecting ChemicalSystem.bonds for the carbon-carbon bond whose removal cuts the molecule.

  • Central inter-ring bond, zero-based atom indices: (5, 6)

  • Central inter-ring bond, AMS one-based atom indices: (6, 7)

  • Inter-ring dihedral, zero-based atom indices: (0, 5, 6, 7)

  • Inter-ring dihedral, AMS one-based atom indices: (1, 6, 7, 8)

The constrained AMS PESScan used DFTB with Model GFN1-xTB, scanning the inter-ring dihedral from 0 to 180 degrees in 19 points, i.e. 10 degree spacing. Energies below are reported relative to the lowest scan point.

Results

Biphenyl torsional profile

Minimum-energy scan point: 140.0 degrees.

Minimum-energy biphenyl twist angle: 40.0 degrees. This is reported as min(dihedral, 180 - dihedral) because the 0 to 180 degree scan has equivalent twisted structures on either side of 90 degrees.

Torsional barrier to the planar structure: 1.8050 kcal/mol. The 0 and 180 degree planar endpoints are both tabulated below; the quoted planar barrier is the lower of the two endpoint relative energies.

Torsional barrier to the perpendicular structure: 1.7045 kcal/mol.

Representative optimized structures

Planar geometry:

Planar biphenyl

Minimum-energy geometry:

Minimum-energy biphenyl

Perpendicular geometry:

Perpendicular biphenyl

Scan table

Dihedral angle (deg)

Absolute energy (Hartree)

Relative energy (kcal/mol)

0.0

-30.7270479157

1.8050

10.0

-30.7276375568

1.4350

20.0

-30.7287351168

0.7463

30.0

-30.7296159661

0.1936

40.0

-30.7299151698

0.0058

50.0

-30.7296468314

0.1742

60.0

-30.7289488005

0.6122

70.0

-30.7281162675

1.1346

80.0

-30.7274575254

1.5480

90.0

-30.7272081545

1.7045

100.0

-30.7274586820

1.5473

110.0

-30.7281246087

1.1294

120.0

-30.7289611334

0.6045

130.0

-30.7296515698

0.1712

140.0

-30.7299244088

0.0000

150.0

-30.7296195325

0.1913

160.0

-30.7287447482

0.7402

170.0

-30.7276400215

1.4335

180.0

-30.7270454035

1.8066

AMS input

The PES scan job was loaded from /home/ubuntu/work/01-run-biphenyl-dftb-pesscan_workdir/biphenyl_dftb_torsion with AMSJob.load_external(...).

PESScan
  Optimize True
  ScanCoordinate
    Dihedral 1 6 7 8 0.0 180.0
    nPoints 19
  End
End

Task PESScan

System
  Atoms
              C       1.5436277587      -0.6712511736      -0.8743423895
              C       2.9379238219      -0.6084386430      -0.7895740467
              C       3.5458648903       0.1540229069       0.2101358580
              C       2.7607157299       0.8559722841       1.1272880062
              C       1.3659731986       0.7976348543       1.0467720204
              C       0.7407365542       0.0321748032       0.0438974615
              C      -0.7407365262      -0.0321759898      -0.0438985615
              C      -1.3882544717      -1.2464975341      -0.3418076249
              C      -2.7829651518      -1.3041919267      -0.4233311075
              C      -3.5458647518      -0.1540214568      -0.2101369285
              C      -2.9156742946       1.0566598609       0.0856160895
              C      -1.5213463694       1.1201124254       0.1693765138
              H       1.0941322198      -1.2547297485      -1.6679429353
              H       3.5475041862      -1.1489839049      -1.5021581640
              H       4.6253028921       0.2009111764       0.2741061689
              H       3.2330362523       1.4435115455       1.9039896331
              H       0.7773312479       1.3360188999       1.7788506547
              H      -0.8179456227      -2.1542017782      -0.4938473594
              H      -3.2727104357      -2.2427544850      -0.6487281126
              H      -4.6253028983      -0.2009093371      -0.2741044826
              H      -3.5078300382       1.9482274962       0.2468998516
              H      -1.0535181915       2.0729097247       0.3829394549
  End
  BondOrders
     1 2 1.5
     1 6 1.5
     1 13 1.0
     2 3 1.5
     2 14 1.0
     3 4 1.5
     3 15 1.0
     4 5 1.5
     4 16 1.0
     5 6 1.5
     5 17 1.0
     6 7 1.0
     7 8 1.5
     7 12 1.5
     8 9 1.5
     8 18 1.0
     9 10 1.5
     9 19 1.0
     10 11 1.5
     10 20 1.0
     11 12 1.5
     11 21 1.0
     12 22 1.0
  End
End

Engine dftb
  Model GFN1-xTB
EndEngine

Conclusion

The DFTB scan places the minimum at a scan dihedral of 140.0 degrees, corresponding to a biphenyl twist of 40.0 degrees. Relative to this point, the lower planar endpoint is 1.8050 kcal/mol higher and the perpendicular point is 1.7045 kcal/mol higher.

Prompts and Python scripts

Prompt (instruction for AI agent)
Use $ams2026

Calculate the torsional potential energy profile of biphenyl with DFTB.

Build biphenyl from SMILES c1ccccc1-c2ccccc2. Identify the central inter-ring
C-C bond and the four atoms defining the inter-ring dihedral by inspecting
the ChemicalSystem bonds.

Run a constrained AMS PES Scan over the inter-ring dihedral from 0 to 180
degrees in 10 degree steps. Use DFTB.

Plot relative energy in kcal/mol vs dihedral angle. In the report, identify
the minimum-energy twist angle and the torsional barriers to the planar and
perpendicular structures.

Include pictures of the optimized structures at the planar geometry, the
minimum-energy geometry, and the perpendicular geometry. Include a table
with dihedral angle, absolute energy, and relative energy for all scan points.
01-run-biphenyl-dftb-pesscan.py
#!/usr/bin/env amspython
from __future__ import annotations

from dataclasses import dataclass

from scm.base import ChemicalSystem
from scm.input_classes import AMS
from scm.plams import AMSJob, Settings, init


SMILES = "c1ccccc1-c2ccccc2"
JOB_NAME = "biphenyl_dftb_torsion"


@dataclass(frozen=True)
class TorsionDefinition:
    central_bond: tuple[int, int]
    dihedral: tuple[int, int, int, int]

    @property
    def ams_dihedral_line(self) -> str:
        a, b, c, d = (idx + 1 for idx in self.dihedral)
        return f"{a} {b} {c} {d} 0.0 180.0"


def carbon_neighbors(system: ChemicalSystem, atom_index: int, exclude: int) -> list[int]:
    return [
        idx
        for idx in system.bonds.get_bonded_atoms(atom_index)
        if idx != exclude and system.atoms[idx].symbol == "C"
    ]


def identify_inter_ring_torsion(system: ChemicalSystem) -> TorsionDefinition:
    candidates: list[tuple[int, int]] = []
    for i, j, _bond in system.bonds:
        if system.atoms[i].symbol != "C" or system.atoms[j].symbol != "C":
            continue
        if system.bond_cuts_molecule(i, j):
            candidates.append((i, j))

    if len(candidates) != 1:
        raise RuntimeError(f"Expected one central inter-ring C-C bond, found {candidates}")

    left, right = candidates[0]
    left_neighbors = carbon_neighbors(system, left, exclude=right)
    right_neighbors = carbon_neighbors(system, right, exclude=left)
    if not left_neighbors or not right_neighbors:
        raise RuntimeError("Could not find ring-neighbor carbon atoms for the inter-ring dihedral")

    return TorsionDefinition(
        central_bond=(left, right),
        dihedral=(left_neighbors[0], left, right, right_neighbors[0]),
    )


def make_settings(torsion: TorsionDefinition) -> Settings:
    settings = Settings()
    settings.input.ams.task = "PESScan"
    settings.input.ams.pesscan.optimize = True
    settings.input.ams.pesscan.scancoordinate.npoints = 19
    settings.input.ams.pesscan.scancoordinate.dihedral = torsion.ams_dihedral_line
    settings.input.dftb.model = "GFN1-xTB"

    AMS.from_settings(settings)
    return settings


def main() -> None:
    init(folder="01-run-biphenyl-dftb-pesscan_workdir")

    system = ChemicalSystem.from_smiles(SMILES)
    torsion = identify_inter_ring_torsion(system)
    settings = make_settings(torsion)

    print(f"Built {system.formula()} from SMILES: {SMILES}")
    print(f"Central inter-ring C-C bond, zero-based: {torsion.central_bond}")
    print(f"Inter-ring dihedral, zero-based: {torsion.dihedral}")
    print(f"AMS dihedral scan line: {torsion.ams_dihedral_line}")

    job = AMSJob(molecule=system, settings=settings, name=JOB_NAME)
    result = job.run()
    if not result.ok():
        raise RuntimeError(f"AMS job failed; inspect {job.path}")


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

from dataclasses import dataclass
from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd
from scm.base import ChemicalSystem, Units
from scm.plams import AMSJob, view


SMILES = "c1ccccc1-c2ccccc2"
JOB_NAME = "biphenyl_dftb_torsion"


@dataclass(frozen=True)
class TorsionDefinition:
    central_bond: tuple[int, int]
    dihedral: tuple[int, int, int, int]

    @property
    def central_bond_one_based(self) -> tuple[int, int]:
        return tuple(idx + 1 for idx in self.central_bond)

    @property
    def dihedral_one_based(self) -> tuple[int, int, int, int]:
        return tuple(idx + 1 for idx in self.dihedral)


def carbon_neighbors(system: ChemicalSystem, atom_index: int, exclude: int) -> list[int]:
    return [
        idx
        for idx in system.bonds.get_bonded_atoms(atom_index)
        if idx != exclude and system.atoms[idx].symbol == "C"
    ]


def identify_inter_ring_torsion(system: ChemicalSystem) -> TorsionDefinition:
    candidates: list[tuple[int, int]] = []
    for i, j, _bond in system.bonds:
        if system.atoms[i].symbol == "C" and system.atoms[j].symbol == "C":
            if system.bond_cuts_molecule(i, j):
                candidates.append((i, j))

    if len(candidates) != 1:
        raise RuntimeError(f"Expected one central inter-ring C-C bond, found {candidates}")

    left, right = candidates[0]
    left_neighbors = carbon_neighbors(system, left, exclude=right)
    right_neighbors = carbon_neighbors(system, right, exclude=left)
    if not left_neighbors or not right_neighbors:
        raise RuntimeError("Could not find ring-neighbor carbon atoms for the inter-ring dihedral")

    return TorsionDefinition(
        central_bond=(left, right),
        dihedral=(left_neighbors[0], left, right, right_neighbors[0]),
    )


def latest_job_path() -> Path:
    candidates = sorted(Path(".").glob("01-run-biphenyl-dftb-pesscan_workdir*/" + JOB_NAME))
    if not candidates:
        raise FileNotFoundError("No biphenyl PES scan job directory was found")
    return candidates[-1]


def pesscan_dataframe(job: AMSJob) -> tuple[pd.DataFrame, list[ChemicalSystem]]:
    results = job.results.get_pesscan_results()
    angles = pd.Series(results["RaveledPESCoords"][0], dtype=float)
    if angles.abs().max() <= 2.0 * 3.141592653589793:
        angles = angles * Units.conversion_factor("radian", "degree")

    energies_hartree = pd.Series(results["PES"], dtype=float)
    min_energy_hartree = energies_hartree.min()
    relative_kcal = (energies_hartree - min_energy_hartree) * Units.conversion_factor("Hartree", "kcal/mol")

    df = pd.DataFrame(
        {
            "Dihedral angle (deg)": angles.round(6),
            "Absolute energy (Hartree)": energies_hartree,
            "Relative energy (kcal/mol)": relative_kcal,
        }
    )
    return df, list(results["Molecules"])


def nearest_index(df: pd.DataFrame, target: float) -> int:
    return int((df["Dihedral angle (deg)"] - target).abs().idxmin())


def write_plot(df: pd.DataFrame) -> None:
    fig, ax = plt.subplots(figsize=(6.5, 4.2))
    ax.plot(df["Dihedral angle (deg)"], df["Relative energy (kcal/mol)"], marker="o", linewidth=1.8)
    ax.set_xlabel("Dihedral angle (deg)")
    ax.set_ylabel("Relative energy (kcal/mol)")
    ax.set_xlim(0, 180)
    ax.grid(True, alpha=0.25)
    fig.tight_layout()
    fig.savefig("biphenyl_torsion_profile.png", dpi=180)
    plt.close(fig)


def write_structure_images(df: pd.DataFrame, molecules: list[ChemicalSystem]) -> dict[str, str]:
    min_idx = int(df["Relative energy (kcal/mol)"].idxmin())
    image_specs = {
        "planar": nearest_index(df, 0.0),
        "minimum": min_idx,
        "perpendicular": nearest_index(df, 90.0),
    }
    output: dict[str, str] = {}
    for label, idx in image_specs.items():
        filename = f"biphenyl_{label}.png"
        view(molecules[idx], width=360, height=280, guess_bonds=True, direction="along_pca3", picture_path=filename)
        output[label] = filename
    return output


def write_report(job: AMSJob, df: pd.DataFrame, images: dict[str, str], torsion: TorsionDefinition) -> None:
    min_idx = int(df["Relative energy (kcal/mol)"].idxmin())
    planar0_idx = nearest_index(df, 0.0)
    planar180_idx = nearest_index(df, 180.0)
    perpendicular_idx = nearest_index(df, 90.0)

    min_angle = df.loc[min_idx, "Dihedral angle (deg)"]
    min_twist = min(min_angle, 180.0 - min_angle)
    planar_barrier = min(
        df.loc[planar0_idx, "Relative energy (kcal/mol)"],
        df.loc[planar180_idx, "Relative energy (kcal/mol)"],
    )
    perpendicular_barrier = df.loc[perpendicular_idx, "Relative energy (kcal/mol)"]

    table = df.copy()
    table["Absolute energy (Hartree)"] = table["Absolute energy (Hartree)"].map(lambda x: f"{x:.10f}")
    table["Relative energy (kcal/mol)"] = table["Relative energy (kcal/mol)"].map(lambda x: f"{x:.4f}")
    table["Dihedral angle (deg)"] = table["Dihedral angle (deg)"].map(lambda x: f"{x:.1f}")

    report = f"""# Biphenyl DFTB torsional PES scan

## Setup

Biphenyl was built from SMILES `{SMILES}` with `ChemicalSystem.from_smiles`. The central inter-ring C-C bond was identified by inspecting `ChemicalSystem.bonds` for the carbon-carbon bond whose removal cuts the molecule.

- Central inter-ring bond, zero-based atom indices: `{torsion.central_bond}`
- Central inter-ring bond, AMS one-based atom indices: `{torsion.central_bond_one_based}`
- Inter-ring dihedral, zero-based atom indices: `{torsion.dihedral}`
- Inter-ring dihedral, AMS one-based atom indices: `{torsion.dihedral_one_based}`

The constrained AMS `PESScan` used DFTB with `Model GFN1-xTB`, scanning the inter-ring dihedral from 0 to 180 degrees in 19 points, i.e. 10 degree spacing. Energies below are reported relative to the lowest scan point.

## Results

![Biphenyl torsional profile](biphenyl_torsion_profile.png)

Minimum-energy scan point: **{min_angle:.1f} degrees**.

Minimum-energy biphenyl twist angle: **{min_twist:.1f} degrees**. This is reported as `min(dihedral, 180 - dihedral)` because the 0 to 180 degree scan has equivalent twisted structures on either side of 90 degrees.

Torsional barrier to the planar structure: **{planar_barrier:.4f} kcal/mol**. The 0 and 180 degree planar endpoints are both tabulated below; the quoted planar barrier is the lower of the two endpoint relative energies.

Torsional barrier to the perpendicular structure: **{perpendicular_barrier:.4f} kcal/mol**.

## Representative optimized structures

Planar geometry:

![Planar biphenyl]({images["planar"]})

Minimum-energy geometry:

![Minimum-energy biphenyl]({images["minimum"]})

Perpendicular geometry:

![Perpendicular biphenyl]({images["perpendicular"]})

## Scan table

{table.to_markdown(index=False, disable_numparse=True)}

## AMS input

The PES scan job was loaded from `{job.path}` with `AMSJob.load_external(...)`.

```ams
{job.get_input()}
```

## Conclusion

The DFTB scan places the minimum at a scan dihedral of {min_angle:.1f} degrees, corresponding to a biphenyl twist of {min_twist:.1f} degrees. Relative to this point, the lower planar endpoint is {planar_barrier:.4f} kcal/mol higher and the perpendicular point is {perpendicular_barrier:.4f} kcal/mol higher.
"""
    Path("report.md").write_text(report)


def main() -> None:
    system = ChemicalSystem.from_smiles(SMILES)
    torsion = identify_inter_ring_torsion(system)

    job = AMSJob.load_external(str(latest_job_path()))
    df, molecules = pesscan_dataframe(job)
    write_plot(df)
    images = write_structure_images(df, molecules)
    write_report(job, df, images, torsion)


if __name__ == "__main__":
    main()
Original Markdown report
# Biphenyl DFTB torsional PES scan

## Setup

Biphenyl was built from SMILES `c1ccccc1-c2ccccc2` with `ChemicalSystem.from_smiles`. The central inter-ring C-C bond was identified by inspecting `ChemicalSystem.bonds` for the carbon-carbon bond whose removal cuts the molecule.

- Central inter-ring bond, zero-based atom indices: `(5, 6)`
- Central inter-ring bond, AMS one-based atom indices: `(6, 7)`
- Inter-ring dihedral, zero-based atom indices: `(0, 5, 6, 7)`
- Inter-ring dihedral, AMS one-based atom indices: `(1, 6, 7, 8)`

The constrained AMS `PESScan` used DFTB with `Model GFN1-xTB`, scanning the inter-ring dihedral from 0 to 180 degrees in 19 points, i.e. 10 degree spacing. Energies below are reported relative to the lowest scan point.

## Results

![Biphenyl torsional profile](biphenyl_torsion_profile.png)

Minimum-energy scan point: **140.0 degrees**.

Minimum-energy biphenyl twist angle: **40.0 degrees**. This is reported as `min(dihedral, 180 - dihedral)` because the 0 to 180 degree scan has equivalent twisted structures on either side of 90 degrees.

Torsional barrier to the planar structure: **1.8050 kcal/mol**. The 0 and 180 degree planar endpoints are both tabulated below; the quoted planar barrier is the lower of the two endpoint relative energies.

Torsional barrier to the perpendicular structure: **1.7045 kcal/mol**.

## Representative optimized structures

Planar geometry:

![Planar biphenyl](biphenyl_planar.png)

Minimum-energy geometry:

![Minimum-energy biphenyl](biphenyl_minimum.png)

Perpendicular geometry:

![Perpendicular biphenyl](biphenyl_perpendicular.png)

## Scan table

| Dihedral angle (deg)   | Absolute energy (Hartree)   | Relative energy (kcal/mol)   |
|:-----------------------|:----------------------------|:-----------------------------|
| 0.0                    | -30.7270479157              | 1.8050                       |
| 10.0                   | -30.7276375568              | 1.4350                       |
| 20.0                   | -30.7287351168              | 0.7463                       |
| 30.0                   | -30.7296159661              | 0.1936                       |
| 40.0                   | -30.7299151698              | 0.0058                       |
| 50.0                   | -30.7296468314              | 0.1742                       |
| 60.0                   | -30.7289488005              | 0.6122                       |
| 70.0                   | -30.7281162675              | 1.1346                       |
| 80.0                   | -30.7274575254              | 1.5480                       |
| 90.0                   | -30.7272081545              | 1.7045                       |
| 100.0                  | -30.7274586820              | 1.5473                       |
| 110.0                  | -30.7281246087              | 1.1294                       |
| 120.0                  | -30.7289611334              | 0.6045                       |
| 130.0                  | -30.7296515698              | 0.1712                       |
| 140.0                  | -30.7299244088              | 0.0000                       |
| 150.0                  | -30.7296195325              | 0.1913                       |
| 160.0                  | -30.7287447482              | 0.7402                       |
| 170.0                  | -30.7276400215              | 1.4335                       |
| 180.0                  | -30.7270454035              | 1.8066                       |

## AMS input

The PES scan job was loaded from `/home/ubuntu/work/01-run-biphenyl-dftb-pesscan_workdir/biphenyl_dftb_torsion` with `AMSJob.load_external(...)`.

```ams
PESScan
  Optimize True
  ScanCoordinate
    Dihedral 1 6 7 8 0.0 180.0
    nPoints 19
  End
End

Task PESScan

System
  Atoms
              C       1.5436277587      -0.6712511736      -0.8743423895
              C       2.9379238219      -0.6084386430      -0.7895740467
              C       3.5458648903       0.1540229069       0.2101358580
              C       2.7607157299       0.8559722841       1.1272880062
              C       1.3659731986       0.7976348543       1.0467720204
              C       0.7407365542       0.0321748032       0.0438974615
              C      -0.7407365262      -0.0321759898      -0.0438985615
              C      -1.3882544717      -1.2464975341      -0.3418076249
              C      -2.7829651518      -1.3041919267      -0.4233311075
              C      -3.5458647518      -0.1540214568      -0.2101369285
              C      -2.9156742946       1.0566598609       0.0856160895
              C      -1.5213463694       1.1201124254       0.1693765138
              H       1.0941322198      -1.2547297485      -1.6679429353
              H       3.5475041862      -1.1489839049      -1.5021581640
              H       4.6253028921       0.2009111764       0.2741061689
              H       3.2330362523       1.4435115455       1.9039896331
              H       0.7773312479       1.3360188999       1.7788506547
              H      -0.8179456227      -2.1542017782      -0.4938473594
              H      -3.2727104357      -2.2427544850      -0.6487281126
              H      -4.6253028983      -0.2009093371      -0.2741044826
              H      -3.5078300382       1.9482274962       0.2468998516
              H      -1.0535181915       2.0729097247       0.3829394549
  End
  BondOrders
     1 2 1.5
     1 6 1.5
     1 13 1.0
     2 3 1.5
     2 14 1.0
     3 4 1.5
     3 15 1.0
     4 5 1.5
     4 16 1.0
     5 6 1.5
     5 17 1.0
     6 7 1.0
     7 8 1.5
     7 12 1.5
     8 9 1.5
     8 18 1.0
     9 10 1.5
     9 19 1.0
     10 11 1.5
     10 20 1.0
     11 12 1.5
     11 21 1.0
     12 22 1.0
  End
End

Engine dftb
  Model GFN1-xTB
EndEngine


```

## Conclusion

The DFTB scan places the minimum at a scan dihedral of 140.0 degrees, corresponding to a biphenyl twist of 40.0 degrees. Relative to this point, the lower planar endpoint is 1.8050 kcal/mol higher and the perpendicular point is 1.7045 kcal/mol higher.