#!/usr/bin/env plams # postprocessing script for calculating surface energies # part of the "Crystals and Surfaces" tutorial in the Amsterdam Modeling Suite # first run the surface_energy_jobs.py script # set the variable jobdir to the directory with calculation results # USAGE: $AMSBIN/plams surface_energy_postprocessing.py from scipy import stats import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt config.erase_workdir = True ################### # set jobdir to the plams working directory where the jobs were run ################### jobdir = "slab_1010_normal" jobs = load_all(jobdir) bulk_e_good = bulk_e_normal = 0 data = [] for path, job in jobs.items(): if job.name == "lattice_optimization": bulk_e_good = job.results.get_energy() n_bulk_atoms = len(job.results.get_main_molecule()) elif job.name == "normalkspace_bulk": bulk_e_normal = job.results.get_energy() else: mol = job.results.get_main_molecule() n_slab_atoms = len(mol) energy = job.results.get_energy() surfacearea = np.linalg.det(toASE(mol).get_cell()[:2, :2]) # square angstrom data.append([n_slab_atoms, energy, surfacearea]) n_atoms_index = 0 e_index = 1 area_index = 2 data = np.array(data) data = data[data[:, n_atoms_index].argsort()] # sort w.r.t first column, i.e. n_atoms print("#atoms, energy, surfacearea") print(data) use_last_n_datapoints = 3 x_data = data[-use_last_n_datapoints:, n_atoms_index] # n_atomss y_data = data[-use_last_n_datapoints:, e_index] # energies slope, intercept, r_value, p_value, std_err = stats.linregress(x_data, y_data) # the intercept = 2*A*ESurf, the area is the same for all jobs Esurf = intercept / (2 * surfacearea) conversion = Units.convert(1.0, "Hartree", "J") / Units.convert(1.0, "angstrom", "m") ** 2 # create a plot max_n_atoms = np.max(data[:, n_atoms_index]) linear_fit_data = np.array([[0, intercept], [max_n_atoms, slope * max_n_atoms + intercept]]) plt.plot(data[:, n_atoms_index], data[:, e_index], "ro", linear_fit_data[:, 0], linear_fit_data[:, 1], "b-") plt.style.use("seaborn") plt.ylabel("Energy (Ha)") plt.xlabel(r"# atoms in ZnO(10$\bar{1}$0) slab") min_x = 0 max_x = int(max_n_atoms * 1.1) min_y = int(data[-1, e_index] * 1.1) max_y = 1 plt.axis([min_x, max_x, min_y, max_y]) plt.annotate( "intercept = {:.6f} Ha".format(intercept), xy=(0.0, 0.0), xytext=(0.1 * (max_x - min_x) + min_x, 0.95 * (max_y - min_y) + min_y), arrowprops={"arrowstyle": "-|>"}, ) plt.title(r"Slab energies for ZnO(10$\bar{1}$0) (k-space quality normal)") plt.savefig("{}.png".format(jobdir)) print("Surface energy from slope (method 1): {}".format(Esurf * conversion)) print("Effective bulk energy: {}".format(slope)) print("Good bulk energy: {}".format(bulk_e_good / n_bulk_atoms)) print("Normal bulk energy: {}".format(bulk_e_normal / n_bulk_atoms)) naive_Esurf = (data[-1, e_index] - (data[-1, n_atoms_index] / n_bulk_atoms) * bulk_e_good) / (2 * surfacearea) naive_Esurf *= conversion print("Naive surface energy w.r.t. good bulk (method 2): {}".format(naive_Esurf)) naive_Esurf = (data[-1, e_index] - (data[-1, n_atoms_index] / n_bulk_atoms) * bulk_e_normal) / (2 * surfacearea) naive_Esurf *= conversion print("Naive surface energy w.r.t. normal bulk (method 2): {}".format(naive_Esurf))