BSSE correction for H2O on ZnO(10-10)

Reproduce the BAND counterpoise correction workflow for water adsorption on ZnO(10-10).

  • Define the full slab+adsorbate structure in an inline AMS System block

  • Run counterpoise calculations for multiple basis sets

  • Compare uncorrected and counterpoise-corrected adsorption energies

  • Plot the adsorption-energy trends versus basis quality

BSSE correction for H2O adsorption on ZnO(10-10)

This example reproduces the BSSE analysis from the Crystals and Surfaces tutorial. It runs BAND counterpoise calculations for five basis sets and plots corrected vs. uncorrected adsorption energies.

Define the slab+molecule system

The full structure is embedded below as an AMS System block, so this notebook does not depend on external files. Atoms 1, 2, and 3 are the adsorbate H2O fragment used in the counterpoise setup.

from typing import Dict, List, Tuple

import matplotlib.pyplot as plt
import scm.plams as plams
from scm.base import ChemicalSystem
from scm.plams import Settings, view
from scm.plams.recipes.counterpoise import CounterpoiseEnergyJob
from scm.utils.conversions import chemsys_to_plams_molecule

ab_system = ChemicalSystem(
    """
System
    Atoms
        H   4.194124   2.321011   9.990591
        O   3.277064   2.369351   9.662700
        H   3.215647   3.302746   9.160403
        O   3.121052   4.371977   8.152437
        O   3.157940   9.599596   8.160532
        O  -0.155188   4.377678   8.158283
        O  -0.092407   9.610401   8.171480
        Zn  3.135686   1.046635   8.068212
        Zn  3.127411   6.251538   7.820969
        Zn -0.123253   6.228628   7.789648
        Zn -0.108077   0.986306   7.727521
        Zn  1.540445   9.053723   7.248234
        Zn  4.781682   9.052463   7.248636
        Zn  1.454711   3.779398   7.208485
        Zn  4.770754   3.793547   7.144487
        O   1.537161   1.798390   7.074007
        O   4.752079   1.794734   7.054472
        O   1.506053   7.034856   7.081453
        O   4.747135   7.034630   7.078047
        O   1.504136   4.449837   5.256964
        O   1.510233   9.664240   5.251216
        O   4.745874   4.467809   5.219866
        O   4.765413   9.642604   5.251871
        Zn  1.527610   1.213182   5.119746
        Zn  4.738978   1.206559   5.113718
        Zn  1.504703   6.460518   5.092362
        Zn  4.750778   6.477069   5.090083
        Zn -0.110739   9.072159   4.273393
        Zn  3.135224   9.070253   4.279334
        Zn -0.117138   3.823484   4.274300
        Zn  3.118895   3.832597   4.260451
        O  -0.115211   1.822988   4.141734
        O   3.131372   1.834356   4.108967
        O  -0.117871   7.064012   4.120018
        O   3.122850   7.064255   4.112580
        O  -0.120166   4.440964   2.316795
        O  -0.116391   9.670504   2.304567
        O   3.130312   4.443558   2.298592
        O   3.130933   9.674750   2.306875
        Zn -0.116763   1.207088   2.147541
        Zn  3.130704   1.213453   2.119764
        Zn -0.119363   6.435899   2.118992
        Zn  3.130940   6.437444   2.109042
        Zn  4.758737   3.618851   1.589370
        Zn  4.755815   8.858895   1.584735
        Zn  1.508461   8.855081   1.581480
        Zn  1.502167   3.625979   1.585735
        O   4.763512   1.779351   1.204246
        O   1.500332   1.785698   1.205474
        O   4.757821   7.015026   1.196018
        O   1.503517   7.011011   1.191741
    End
    Lattice
        6.50000095   0.0   0.0
        0.0   10.46000194   0.0
    End
End
"""
)

view(ab_system, width=700, height=300, direction="tilt_x", guess_bonds=True)
image generated from notebook

Run counterpoise jobs for each basis set

This section can take significant time. For production use, run on appropriate compute resources.

ab_mol = chemsys_to_plams_molecule(ab_system)
ids_state_A: List[int] = [1, 2, 3]  # H2O fragment
basis_sets: List[str] = ["DZ", "DZP", "TZP", "TZ2P", "QZ4P"]

base_settings = Settings()
base_settings.input.ams.Task = "GeometryOptimization"
base_settings.input.band.KSpace.Quality = "Basic"
base_settings.input.band.XC.GGA = "PBE"

jobs: Dict[str, CounterpoiseEnergyJob] = {}
for basis in basis_sets:
    settings = base_settings.copy()
    settings.input.band.Basis.Type = basis
    job = CounterpoiseEnergyJob(
        name=f"{basis}_counterpoise",
        AB=ab_mol.copy(),
        ids_state_A=ids_state_A,
        settings_state_AB=settings,
    )
    job.run()
    jobs[basis] = job
[13.03|12:35:35] JOB DZ_counterpoise STARTED
[13.03|12:35:35] JOB DZ_counterpoise RUNNING
[13.03|12:35:35] JOB DZ_counterpoise/AB_geo_AB_basis_AB STARTED
[13.03|12:35:35] JOB DZ_counterpoise/AB_geo_AB_basis_AB RUNNING
[13.03|12:56:26] JOB DZ_counterpoise/AB_geo_AB_basis_AB FINISHED
[13.03|12:56:26] JOB DZ_counterpoise/AB_geo_AB_basis_AB SUCCESSFUL
[13.03|12:56:26] JOB DZ_counterpoise/A_geo_AB_basis_AB STARTED
[13.03|12:56:26] JOB DZ_counterpoise/A_geo_AB_basis_AB RUNNING
[13.03|12:57:05] JOB DZ_counterpoise/A_geo_AB_basis_AB FINISHED
[13.03|12:57:05] JOB DZ_counterpoise/A_geo_AB_basis_AB SUCCESSFUL
[13.03|12:57:05] JOB DZ_counterpoise/A_geo_AB_basis_A STARTED
[13.03|12:57:05] JOB DZ_counterpoise/A_geo_AB_basis_A RUNNING
... output trimmed ....
[13.03|18:38:03] JOB QZ4P_counterpoise/B_geo_AB_basis_B FINISHED
[13.03|18:38:03] JOB QZ4P_counterpoise/B_geo_AB_basis_B SUCCESSFUL
[13.03|18:38:03] JOB QZ4P_counterpoise/A_geo_A_basis_A STARTED
[13.03|18:38:03] JOB QZ4P_counterpoise/A_geo_A_basis_A RUNNING
[13.03|18:40:15] JOB QZ4P_counterpoise/A_geo_A_basis_A FINISHED
[13.03|18:40:15] JOB QZ4P_counterpoise/A_geo_A_basis_A SUCCESSFUL
[13.03|18:40:15] JOB QZ4P_counterpoise/B_geo_B_basis_B STARTED
[13.03|18:40:15] JOB QZ4P_counterpoise/B_geo_B_basis_B RUNNING
[13.03|20:23:50] JOB QZ4P_counterpoise/B_geo_B_basis_B FINISHED
[13.03|20:23:50] JOB QZ4P_counterpoise/B_geo_B_basis_B SUCCESSFUL
[13.03|20:23:50] JOB QZ4P_counterpoise FINISHED
[13.03|20:23:50] JOB QZ4P_counterpoise SUCCESSFUL
rows: List[Tuple[str, float, float, float]] = []
for basis in basis_sets:
    energies = jobs[basis].results.get_all_energies(unit="kcal/mol")
    rows.append((basis, energies["Ebind_raw"], energies["Ebind_cp"], -energies["BSSE_tot"]))

print("Basis  Ebind_uncorrected  Ebind_corrected  Counterpoise_correction")
for basis, raw, corrected, cp_corr in rows:
    print(f"{basis:5s}  {raw:17.2f}  {corrected:15.2f}  {cp_corr:24.2f}")
Basis  Ebind_uncorrected  Ebind_corrected  Counterpoise_correction
DZ                -37.83           -34.13                      3.70
DZP               -27.05           -24.05                      2.99
TZP               -24.50           -23.19                      1.31
TZ2P              -23.74           -22.59                      1.14
QZ4P              -22.43           -22.28                      0.14

Plot adsorption energies

labels = [r[0] for r in rows]
uncorrected = [r[1] for r in rows]
corrected = [r[2] for r in rows]

x = list(range(len(labels)))
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x, corrected, "o-", label="Counterpoise-corrected")
ax.plot(x, uncorrected, "o-", label="Uncorrected")
ax.set_title("H2O adsorption on ZnO(10-10)")
ax.set_xlabel("Basis set")
ax.set_ylabel("Adsorption energy (kcal/mol)")
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()
fig.tight_layout()
ax;
image generated from notebook

See also

Python Script

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

# ## BSSE correction for H2O adsorption on ZnO(10-10)
#
# This example reproduces the BSSE analysis from the *Crystals and Surfaces* tutorial.
# It runs BAND counterpoise calculations for five basis sets and plots corrected vs. uncorrected adsorption energies.
#

# ## Define the slab+molecule system
#
# The full structure is embedded below as an AMS `System` block, so this notebook does not depend on external files.
# Atoms 1, 2, and 3 are the adsorbate H2O fragment used in the counterpoise setup.
#

from typing import Dict, List, Tuple

import matplotlib.pyplot as plt
import scm.plams as plams
from scm.base import ChemicalSystem
from scm.plams import Settings, view
from scm.plams.recipes.counterpoise import CounterpoiseEnergyJob
from scm.utils.conversions import chemsys_to_plams_molecule

ab_system = ChemicalSystem(
    """
System
    Atoms
        H   4.194124   2.321011   9.990591
        O   3.277064   2.369351   9.662700
        H   3.215647   3.302746   9.160403
        O   3.121052   4.371977   8.152437
        O   3.157940   9.599596   8.160532
        O  -0.155188   4.377678   8.158283
        O  -0.092407   9.610401   8.171480
        Zn  3.135686   1.046635   8.068212
        Zn  3.127411   6.251538   7.820969
        Zn -0.123253   6.228628   7.789648
        Zn -0.108077   0.986306   7.727521
        Zn  1.540445   9.053723   7.248234
        Zn  4.781682   9.052463   7.248636
        Zn  1.454711   3.779398   7.208485
        Zn  4.770754   3.793547   7.144487
        O   1.537161   1.798390   7.074007
        O   4.752079   1.794734   7.054472
        O   1.506053   7.034856   7.081453
        O   4.747135   7.034630   7.078047
        O   1.504136   4.449837   5.256964
        O   1.510233   9.664240   5.251216
        O   4.745874   4.467809   5.219866
        O   4.765413   9.642604   5.251871
        Zn  1.527610   1.213182   5.119746
        Zn  4.738978   1.206559   5.113718
        Zn  1.504703   6.460518   5.092362
        Zn  4.750778   6.477069   5.090083
        Zn -0.110739   9.072159   4.273393
        Zn  3.135224   9.070253   4.279334
        Zn -0.117138   3.823484   4.274300
        Zn  3.118895   3.832597   4.260451
        O  -0.115211   1.822988   4.141734
        O   3.131372   1.834356   4.108967
        O  -0.117871   7.064012   4.120018
        O   3.122850   7.064255   4.112580
        O  -0.120166   4.440964   2.316795
        O  -0.116391   9.670504   2.304567
        O   3.130312   4.443558   2.298592
        O   3.130933   9.674750   2.306875
        Zn -0.116763   1.207088   2.147541
        Zn  3.130704   1.213453   2.119764
        Zn -0.119363   6.435899   2.118992
        Zn  3.130940   6.437444   2.109042
        Zn  4.758737   3.618851   1.589370
        Zn  4.755815   8.858895   1.584735
        Zn  1.508461   8.855081   1.581480
        Zn  1.502167   3.625979   1.585735
        O   4.763512   1.779351   1.204246
        O   1.500332   1.785698   1.205474
        O   4.757821   7.015026   1.196018
        O   1.503517   7.011011   1.191741
    End
    Lattice
        6.50000095   0.0   0.0
        0.0   10.46000194   0.0
    End
End
"""
)

view(ab_system, width=700, height=300, direction="tilt_x", guess_bonds=True, picture_path="picture1.png")


# ## Run counterpoise jobs for each basis set
#
# This section can take significant time. For production use, run on appropriate compute resources.
#

ab_mol = chemsys_to_plams_molecule(ab_system)
ids_state_A: List[int] = [1, 2, 3]  # H2O fragment
basis_sets: List[str] = ["DZ", "DZP", "TZP", "TZ2P", "QZ4P"]

base_settings = Settings()
base_settings.input.ams.Task = "GeometryOptimization"
base_settings.input.band.KSpace.Quality = "Basic"
base_settings.input.band.XC.GGA = "PBE"

jobs: Dict[str, CounterpoiseEnergyJob] = {}
for basis in basis_sets:
    settings = base_settings.copy()
    settings.input.band.Basis.Type = basis
    job = CounterpoiseEnergyJob(
        name=f"{basis}_counterpoise",
        AB=ab_mol.copy(),
        ids_state_A=ids_state_A,
        settings_state_AB=settings,
    )
    job.run()
    jobs[basis] = job


rows: List[Tuple[str, float, float, float]] = []
for basis in basis_sets:
    energies = jobs[basis].results.get_all_energies(unit="kcal/mol")
    rows.append((basis, energies["Ebind_raw"], energies["Ebind_cp"], -energies["BSSE_tot"]))

print("Basis  Ebind_uncorrected  Ebind_corrected  Counterpoise_correction")
for basis, raw, corrected, cp_corr in rows:
    print(f"{basis:5s}  {raw:17.2f}  {corrected:15.2f}  {cp_corr:24.2f}")


# ## Plot adsorption energies
#

labels = [r[0] for r in rows]
uncorrected = [r[1] for r in rows]
corrected = [r[2] for r in rows]

x = list(range(len(labels)))
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x, corrected, "o-", label="Counterpoise-corrected")
ax.plot(x, uncorrected, "o-", label="Uncorrected")
ax.set_title("H2O adsorption on ZnO(10-10)")
ax.set_xlabel("Basis set")
ax.set_ylabel("Adsorption energy (kcal/mol)")
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()
fig.tight_layout()
ax
ax.figure.savefig("picture2.png")