nepy: NEP C++ interface

This tutorial demonstrates the basic functionality of nepy. nepy interfaces directly with the GPUMD NEP object, and enables analysis of trained NEP models. Specifically, nepy exposes convenience functions for calculating per-atom descriptors, energies, forces and virials/stresses for ASE Atoms objects, as well as a CPU-only ASE calculator.

Note: ``nepy`` only supports NEP potentials created with GPUMD version 3.3 and later

Calculate descriptors, energies and forces

We start by creating an Atoms object which will serve as our prototype system for the extent of this tutorial, and importing the get_descriptors and get_potential_forces_and_virials functions from nepy.

[1]:
from calorine.nepy import get_descriptors, get_potential_forces_and_virials
from ase.build import bulk

atoms = bulk('PbTe', crystalstructure='rocksalt', a=6.7)

We can now compute the descriptors, energies, forces and virials for our system by calling get_descriptors and get_potential_forces_and_virials and passing in our system together with a path to a NEP model file (often called nep.txt). Here, we use a NEP3 model for PbTe.

[2]:
descriptors = get_descriptors(
    atoms,
    potential_filename='PbTe_NEP3.txt'
)

per_atom_energies, forces, virials = get_potential_forces_and_virials(
    atoms,
    potential_filename='PbTe_NEP3.txt'
)

print(f'Shape of descriptors: {descriptors.shape}')
print(f'Energy (eV):\n {per_atom_energies}')
print(f'Forces (eV/Å):\n {forces}')
print(f'Virials (eV):\n {virials}')
Shape of descriptors: (2, 30)
Energy (eV):
 [-3.81130494 -3.86976582]
Forces (eV/Å):
 [[ 2.70616862e-16  2.85239922e-17 -1.16068684e-16]
 [-2.70616862e-16 -2.85239922e-17  1.16068684e-16]]
Virials (eV):
 [[-2.34181544e-01 -2.65703027e-16  1.22107929e-16 -2.57029410e-16
  -2.34181544e-01  2.64354885e-17  1.42924611e-16  2.64354885e-17
  -2.34181544e-01]
 [-1.28333417e-01 -4.71844785e-16  4.16333634e-17 -4.71844785e-16
  -1.28333417e-01 -8.32667268e-17  1.38777878e-17 -8.32667268e-17
  -1.28333417e-01]]

get_descriptors can also be called without supplying a NEP model file, in which case a dummy NEP2 model will be generated and used. The dummy model has all parameter values set to 1. Note that this model does not differentiate different atom species. Thus, supplying your own trained NEP model file is recommended for most applications.

[3]:
descriptors = get_descriptors(atoms)
print(f'Shape of descriptors: {descriptors.shape}')
Shape of descriptors: (2, 52)

CPU-only ASE calculator

nepy implements a CPU-only ASE calculator to enable using trained NEP models on systems without a GPU. At the moment, the calculator only exposes functionality for calculating energies and forces.

We use the same Atoms object as in the previous section, and create calculator instance by specifying the path to a NEP model file.

Basic usage

[4]:
from calorine.nepy import CPUNEP
from ase.build import bulk

atoms = bulk('PbTe', crystalstructure='rocksalt', a=6.7)
calc = CPUNEP('PbTe_NEP3.txt')
atoms.calc = calc

With the system and calculator defined, we can now calculate the energies and forces:

[5]:
print('Energy (eV):', atoms.get_potential_energy())
print('Forces (eV/Å):\n', atoms.get_forces())
Energy (eV): -7.681070759348718
Forces (eV/Å):
 [[ 2.70616862e-16  2.85239922e-17 -1.16068684e-16]
 [-2.70616862e-16 -2.85239922e-17  1.16068684e-16]]

Calculate energy-volume curve

The capabilities of the CPU-only ASE calculator is similar to those of the GPU-based ASE calculator GPUNEP also implemented in calorine. As an example, here we use the CPU calculator to calculate the energy-volume curve with the PbTe potential, which is also done in the tutorial for the GPU-based ASE calculator.

[6]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

energies = []
volumes = []

atoms_copy = atoms.copy()
original_cell = atoms.cell.copy()

atoms_copy.calc = calc

for scale in np.arange(0.9, 1.11, 0.01):
    atoms_copy.set_cell(scale * original_cell, scale_atoms=True)
    volumes.append(atoms_copy.get_volume())
    energies.append(atoms_copy.get_potential_energy() / len(atoms_copy))

fig, ax = plt.subplots()
ax.plot(volumes, energies, '-o')
ax.set_xlabel('Volume (Å^3)')
ax.set_ylabel('Energy (eV/atom)')
[6]:
Text(0, 0.5, 'Energy (eV/atom)')
../_images/tutorials_nepy_13_1.png