3.6. GFN1-xTB: Bandstructure of Silicon

This example shows how to use PLAMS and ParAMS to refit the bandstructure of silicon from GFN1-xTB based on BAND reference calculation. We also include in the fitting, the repulsive part of the potential to improve the description of the silicon volume scan. We start with some imports.

import scm.plams as plams
import scm.params as params
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
import os
import numpy as np
plams.init()
PLAMS working folder: /Users/nonofrio/Documents/amshome_fix2024/scripting/scm/params/doc/source/examples/xtb_si/plams_workdir

3.6.1. Some settings and functions

We first define some settings to run BAND, xTB, and some tasks such as Single Point and equation of state (EOS).

s_band = plams.Settings()
s_band.input.BAND.Basis.Type = "TZP"
s_band.input.BAND.XC.GGA = "PBE"
s_band.input.BAND.XC.DISPERSION = "Grimme3"
s_band.input.BAND.BandStructure.Enabled = "Yes"
s_xTB = plams.Settings()
s_xTB.input.dftb.model = 'GFN1-xTB'
s_sp = plams.Settings()
s_sp.input.ams.Task = "SinglePoint"
s_eos = plams.Settings()
s_eos.input.ams.Task = "pesscan"
s_eos.input.ams.PESScan.ScanCoordinate.NPoints = 5
s_eos.input.ams.PESScan.ScanCoordinate.CellVolumeScalingRange = "0.80 1.20"
s_eos.input.ams.PESScan.CalcPropertiesAtPESPoints = "Yes"
s_eos.input.ams.Properties.StressTensor = "Yes"
s_eos.input.ams.Properties.Gradients = "Yes"

Some functions to setup the jobs, extract the EOS, plot the EOS and bandstructure.

def get_job(settings, name, mol):
    job = plams.AMSJob(
        settings=settings,
        molecule=mol,
        name=name
    )
    return job
def get_eos(job):
    to_A = plams.Units.convert(1, 'bohr', 'angstrom')
    to_eV = plams.Units.convert(1, 'au', 'eV')
    vols = np.array(job.results.readrkf('PESScan', 'PESCoords')[0::7])*to_A*to_A*to_A
    energies = np.array(job.results.readrkf('PESScan', 'PES'))*to_eV
    return vols, energies
def plot_eos(x_list,y_list,name_list):
    plt.figure()
    clist = ['b','r','g']
    for i in range(len(x_list)):
        plt.plot(x_list[i],y_list[i]-min(y_list[i]),label=name_list[i],color=clist[i])
    plt.xlabel("Volume in A^3")
    plt.ylabel("Energy in eV")
    plt.legend()
    plt.savefig(f"eos{'_'.join(name_list)}.pdf")
def plot_bands(job,name):

    x, y_spin_up, y_spin_down, labels, fermi_energy = job.results.get_band_structure(unit='eV')
    plams.plot_band_structure(x, y_spin_up, None, labels, fermi_energy, zero='fermi')
    plt.ylim(-10, 10)
    plt.ylabel('$E - E_{Fermi}$ (eV)')
    plt.xlabel('Path')
    plt.title(name)
    plt.savefig(f"bands_{name}.pdf")
def get_bandgap(job, name):
    toeV = plams.Units.convert(1.0, "hartree", "eV")
    topvb = job.results.readrkf("BandStructure", "TopValenceBand", file="engine") * toeV
    bottomcb = job.results.readrkf("BandStructure", "BottomConductionBand", file="engine") * toeV
    gap = bottomcb - topvb
#    print(f"Results ({name}):")
#    print(f"Top of valence band:       {topvb:.2f} eV")
#    print(f"Bottom of conduction band: {bottomcb:.2f} eV")
    print(f"{name} bandgap:                  {gap:.2f} eV")

3.6.2. Si unit cell

Si_unit = plams.AMSJob.from_input(
    """
    System
        Atoms
            Si -0.67875 -0.67875 -0.67875
            Si 0.67875 0.67875 0.67875
        End
        Lattice
            0.0 2.715 2.715
            2.715 0.0 2.715
            2.715 2.715 0.0
        End
    End
"""
).molecule[""]
plams.plot_molecule(Si_unit)
../../_images/Si_band_15_0.png

3.6.3. EOS of Silicon

Let’s perform an EOS of Silicon with BAND and xTB and compare the results.

band_eos_job = get_job(s_band + s_eos, "Si_volumescan_BAND", Si_unit)
band_eos_job.run()
[10.07|10:18:02] JOB Si_volumescan_BAND STARTED
[10.07|10:18:02] JOB Si_volumescan_BAND RUNNING
[10.07|10:26:27] JOB Si_volumescan_BAND FINISHED
[10.07|10:26:27] JOB Si_volumescan_BAND SUCCESSFUL
<scm.plams.interfaces.adfsuite.ams.AMSResults at 0x16417e760>
xTB_eos_job = get_job(s_xTB + s_eos, "Si_volumescan_xTB", Si_unit)
xTB_eos_job.run()
[10.07|10:34:31] JOB Si_volumescan_xTB STARTED
[10.07|10:34:31] JOB Si_volumescan_xTB RUNNING
[10.07|10:35:24] JOB Si_volumescan_xTB FINISHED
[10.07|10:35:24] JOB Si_volumescan_xTB SUCCESSFUL
<scm.plams.interfaces.adfsuite.ams.AMSResults at 0x16417e940>
x_band,y_band = get_eos(band_eos_job)
x_xTB,y_xTB = get_eos(xTB_eos_job)
plot_eos([x_band,x_xTB],[y_band,y_xTB],["BAND","xTB"])
../../_images/Si_band_19_0.png

3.6.4. Bandstructure of Silicon

Similarly we now perform the bandstructure calculation of Silicon with BAND and xTB and compare the results.

band_bandstructure_job = get_job(s_band + s_sp, "Si_bandstructure_BAND", Si_unit)
band_bandstructure_job.run()
[10.07|10:35:48] JOB Si_bandstructure_BAND STARTED
[10.07|10:35:48] JOB Si_bandstructure_BAND RUNNING
[10.07|10:36:33] JOB Si_bandstructure_BAND FINISHED
[10.07|10:36:33] JOB Si_bandstructure_BAND SUCCESSFUL
<scm.plams.interfaces.adfsuite.ams.AMSResults at 0x1652743a0>
xTB_bandstructure_job = get_job(s_xTB + s_sp, "Si_bandstructure_xTB", Si_unit)
xTB_bandstructure_job.run()
[10.07|10:38:49] JOB Si_bandstructure_xTB STARTED
[10.07|10:38:49] JOB Si_bandstructure_xTB RUNNING
[10.07|10:38:53] JOB Si_bandstructure_xTB FINISHED
[10.07|10:38:53] JOB Si_bandstructure_xTB SUCCESSFUL
<scm.plams.interfaces.adfsuite.ams.AMSResults at 0x16520cd00>
plot_bands(band_bandstructure_job,"BAND")
plot_bands(xTB_bandstructure_job,"xTB")
../../_images/Si_band_23_0.png ../../_images/Si_band_23_1.png
get_bandgap(band_bandstructure_job,"BAND")
get_bandgap(xTB_bandstructure_job,"xTB")
BAND bandgap:                  0.82 eV
xTB bandgap:                  2.77 eV

3.6.5. ParAMS optimization

We now setup ParAMS to optimize xTB parameters to improve the bandstructure of Silicon. Note that we also include the EOS of Silicon to maintain and improve the proper geometric description of the crystal during the optimization.

results_importer_settings = plams.Settings()
results_importer_settings.units.energy = 'eV'
results_importer_settings.units.forces = 'eV/angstrom'
ri = params.ResultsImporter(settings=results_importer_settings)

Here we import 10 bands of the bandstructure, as well as the bandgap.

bands_list = list(range(10))
ri.add_singlejob(band_bandstructure_job.results.rkfpath(),properties={'bandstructure(bands='+str(bands_list)+',only_high_symmetry_points=True)': {'unit': 'eV'}})
ri.add_singlejob(band_bandstructure_job.results.rkfpath(),properties={'bandgap':{'weight': 10.0}})
["bandgap('Si_bandstructure_BAND')"]

We import the EOS.

ri.add_pesscan_singlepoints(band_eos_job.results.rkfpath(),
    properties={'relative_energies': {'relative_to': 'min','unit': 'eV', 'weight': 2.0},
               'forces': {'unit': 'eV/angstrom'},
               'stresstensor_diagonal_3d': {'unit': 'GPa'},
               })
["energy('Si_volumescan_BAND_frame001')-energy('Si_volumescan_BAND_frame003')",
 "energy('Si_volumescan_BAND_frame002')-energy('Si_volumescan_BAND_frame003')",
 "energy('Si_volumescan_BAND_frame004')-energy('Si_volumescan_BAND_frame003')",
 "energy('Si_volumescan_BAND_frame005')-energy('Si_volumescan_BAND_frame003')",
 "forces('Si_volumescan_BAND_frame001')",
 "forces('Si_volumescan_BAND_frame002')",
 "forces('Si_volumescan_BAND_frame003')",
 "forces('Si_volumescan_BAND_frame004')",
 "forces('Si_volumescan_BAND_frame005')",
 "stresstensor_diagonal_3d('Si_volumescan_BAND_frame001')",
 "stresstensor_diagonal_3d('Si_volumescan_BAND_frame002')",
 "stresstensor_diagonal_3d('Si_volumescan_BAND_frame003')",
 "stresstensor_diagonal_3d('Si_volumescan_BAND_frame004')",
 "stresstensor_diagonal_3d('Si_volumescan_BAND_frame005')"]
params_folder = "params_Si"
ri.store(folder=params_folder, backup=True)
['params_Si/job_collection.yaml',
 'params_Si/results_importer_settings.yaml',
 'params_Si/training_set.yaml']

Below we define the xTB parameters to optimize.

s = plams.Settings()
interface = params.GFN1xTBParameters(settings=s)
interface['Si:Basis.3p.H_l'].is_active = True
interface['Si:Basis.3p.k_l'].is_active = True
interface['Si:Basis.3p.k_poly'].is_active = True
interface['Si:Basis.3p.zeta_l'].is_active = True

interface['Si:Basis.3s.H_l'].is_active = True
interface['Si:Basis.3s.k_l'].is_active = True
interface['Si:Basis.3s.k_poly'].is_active = True
interface['Si:Basis.3s.zeta_l'].is_active = True

interface['Si:alpha'].is_active = True
interface['Si:eta'].is_active = True
interface['Si:Gamma'].is_active = True
interface['Si:Z'].is_active = True
interface.yaml_store(os.path.join(params_folder,'parameter_interface.yaml'))

And finally some settings for ParAMS.

job = params.ParAMSJob.from_yaml(params_folder, use_relative_paths=True)
job.settings.input.Task = "Optimization"
job.add_optimizer("CMAES", {'Sigma0': 0.01, 'PopSize': 4})
job.add_exit_condition("MaxTotalFunctionCalls", 100)
job.parameter_interface = os.path.join(params_folder,'parameter_interface.yaml')
job.training_set = os.path.join(params_folder,'training_set.yaml')
job.job_collection = os.path.join(params_folder,'job_collection.yaml')
job.training_set.UsePipe = "No"
job.run()
[10.07|10:39:40] JOB plamsjob STARTED
[10.07|10:39:40] JOB plamsjob RUNNING
[10.07|10:57:00] JOB plamsjob FINISHED
[10.07|10:57:00] JOB plamsjob SUCCESSFUL
<scm.params.plams.paramsjob.ParAMSResults at 0x16550f280>

We can extract and plot the loss function.

epoch, training_loss = job.results.get_running_loss(data_set="training_set")
plt.figure()
plt.plot(epoch, training_loss)
plt.ylabel("Training loss")
plt.xlabel('Epoch')
#plt.savefig(f"loss.pdf")
Text(0.5, 0, 'Epoch')
../../_images/Si_band_40_1.png

3.6.6. Validation

Now we can recompute and plot the bandstructure and EOS with the optimized xTB parameters.

s_optxTB = plams.Settings()
s_optxTB.input.dftb.model = "GFN1-xTB"
s_optxTB.input.dftb.ResourcesDir = job.results.get_xtb_files()
optxTB_bandstructure_job = get_job(s_optxTB + s_sp, "Si_bandstructure_optxTB", Si_unit)
optxTB_bandstructure_job.run();
[10.07|11:02:21] JOB Si_bandstructure_optxTB STARTED
[10.07|11:02:21] JOB Si_bandstructure_optxTB RUNNING
[10.07|11:02:24] JOB Si_bandstructure_optxTB FINISHED
[10.07|11:02:24] JOB Si_bandstructure_optxTB SUCCESSFUL
plot_bands(band_bandstructure_job,"BAND")
plot_bands(xTB_bandstructure_job,"xTB")
plot_bands(optxTB_bandstructure_job,"optxTB")
../../_images/Si_band_44_0.png ../../_images/Si_band_44_1.png ../../_images/Si_band_44_2.png
get_bandgap(band_bandstructure_job,"BAND")
get_bandgap(xTB_bandstructure_job,"xTB")
get_bandgap(optxTB_bandstructure_job,"optxTB")
BAND bandgap:                  0.82 eV
xTB bandgap:                  2.77 eV
optxTB bandgap:                  0.95 eV
optxTB_eos_job = get_job(s_optxTB + s_eos, "Si_volumescan_optxTB", Si_unit)
optxTB_eos_job.run();
[10.07|11:02:37] JOB Si_volumescan_optxTB STARTED
[10.07|11:02:37] JOB Si_volumescan_optxTB RUNNING
[10.07|11:03:01] JOB Si_volumescan_optxTB FINISHED
[10.07|11:03:01] JOB Si_volumescan_optxTB SUCCESSFUL
x_optxTB,y_optxTB = get_eos(optxTB_eos_job)
plot_eos([x_band,x_xTB,x_optxTB],[y_band,y_xTB,y_optxTB],["BAND","xTB","optxTB"])
plot_eos([x_band,x_optxTB],[y_band,y_optxTB],["BAND","optxTB"])
../../_images/Si_band_47_0.png ../../_images/Si_band_47_1.png

What to do next:

  • use a more accurate functional in BAND for the electronic structure (HSE06)

  • adjust weights to get a better agreement between electronic and atomic structure

plams.finish()
[09.07|18:47:17] PLAMS run finished. Goodbye