Expert assistance with the Atomic Simulation Environment (ASE) Python library for atomistic simulations, including structure building, calculator setup, optimization, dynamics, and analysis
This skill provides expert guidance for working with the Atomic Simulation Environment (ASE), a Python library for setting up, manipulating, running, visualizing, and analyzing atomistic simulations.
Use this skill when:
The fundamental ASE object representing atomic structures:
Interface to quantum chemistry and materials science codes:
atoms.calc = calculatorGeometry optimization algorithms:
Molecular dynamics simulations:
from ase import Atoms
from ase.build import bulk, molecule, surface, add_adsorbate
# Bulk crystals
atoms = bulk('Cu', 'fcc', a=3.6)
atoms = bulk('Si', 'diamond', a=5.43)
# Molecules
atoms = molecule('H2O')
atoms = molecule('CH4')
# Surfaces
slab = surface('Au', (1, 1, 1), size=(4, 4, 4), vacuum=10.0)
# Add adsorbate to surface
add_adsorbate(slab, 'O', height=1.5, position='fcc')
# EMT calculator (for testing)
from ase.calculators.emt import EMT
atoms.calc = EMT()
# VASP calculator
from ase.calculators.vasp import Vasp
calc = Vasp(
xc='PBE',
encut=400,
kpts=(4, 4, 4),
ismear=1,
sigma=0.1,
ibrion=2,
nsw=100,
ediff=1e-5,
)
atoms.calc = calc
# GPAW calculator
from gpaw import GPAW, PW
calc = GPAW(
mode=PW(400),
xc='PBE',
kpts=(4, 4, 4),
txt='output.txt'
)
atoms.calc = calc
from ase.optimize import BFGS, LBFGS, FIRE
# Basic optimization
opt = BFGS(atoms, trajectory='opt.traj')
opt.run(fmax=0.05) # converge until forces < 0.05 eV/Å
# With constraints (fix bottom layers)
from ase.constraints import FixAtoms
mask = [atom.position[2] < 10.0 for atom in atoms]
atoms.set_constraint(FixAtoms(mask=mask))
opt = BFGS(atoms)
opt.run(fmax=0.05)
# Log optimization progress
opt = BFGS(atoms, logfile='opt.log', trajectory='opt.traj')
opt.run(fmax=0.01, steps=100)
from ase.md.velocitydistribution import MaxwellBoltzmannDistribution
from ase.md.langevin import Langevin
from ase import units
# Set initial velocities
MaxwellBoltzmannDistribution(atoms, temperature_K=300)
# NVT dynamics with Langevin thermostat
dyn = Langevin(
atoms,
timestep=1.0 * units.fs,
temperature_K=300,
friction=0.002
)
# Run with trajectory output
from ase.io.trajectory import Trajectory
traj = Trajectory('md.traj', 'w', atoms)
dyn.attach(traj.write, interval=10)
dyn.run(5000) # 5000 steps
from ase.io import read, write
# Read trajectory
traj = read('opt.traj', ':') # ':' reads all frames
final = traj[-1] # last frame
# Extract energies
energies = [atoms.get_potential_energy() for atoms in traj]
# Analyze forces
forces = final.get_forces()
max_force = max(np.linalg.norm(forces, axis=1))
# Calculate distances
from ase.geometry import get_distances
d = atoms.get_distance(0, 1) # distance between atoms 0 and 1
# Write to various formats
write('structure.cif', atoms)
write('structure.xyz', atoms)
write('POSCAR', atoms) # VASP format
from ase.dft.kpoints import bandpath
# Get high-symmetry k-point path
atoms = bulk('Si', 'diamond', a=5.43)
path = atoms.cell.bandpath('GXWLG', npoints=100)
# Calculate bands (example with GPAW)
calc = GPAW(
mode=PW(400),
xc='PBE',
kpts={'path': path, 'density': 10}
)
atoms.calc = calc
atoms.get_potential_energy() # run calculation
# Plot bands
bs = calc.band_structure()
bs.plot(filename='bands.png', show=True)
Always check your structure before running expensive calculations:
# Visualize structure
from ase.visualize import view
view(atoms) # requires GUI
# Check for overlapping atoms
from ase.geometry import get_distances
distances = get_distances(atoms.positions)[1]
min_distance = distances[distances > 0].min()
print(f"Minimum distance: {min_distance:.3f} Å")
# Verify cell and PBC
print(f"Cell: {atoms.cell}")
print(f"PBC: {atoms.pbc}")
# Always attach calculator before accessing properties
if atoms.calc is None:
raise RuntimeError("No calculator attached")
# Reuse calculator for multiple structures
calc = Vasp(xc='PBE', encut=400)
for structure in structures:
structure.calc = calc
energy = structure.get_potential_energy()
from ase.calculators.calculator import CalculationFailed
try:
energy = atoms.get_potential_energy()
except CalculationFailed as e:
print(f"Calculation failed: {e}")
# Handle error or retry with different parameters
# Use trajectory files efficiently
from ase.io import read
# Don't read entire trajectory if you only need specific frames
last_frame = read('md.traj', -1) # only last frame
every_10th = read('md.traj', '::10') # every 10th frame
# For VASP: use selective dynamics for large systems
from ase.constraints import FixAtoms
# Fix bulk atoms, only relax surface/adsorbate
from ase.build import fcc111, add_adsorbate
from ase.optimize import BFGS
# 1. Create and optimize clean surface
slab = fcc111('Pt', size=(4, 4, 4), vacuum=10.0)
slab.calc = EMT()
opt = BFGS(slab)
opt.run(fmax=0.05)
E_slab = slab.get_potential_energy()
# 2. Add adsorbate and optimize
add_adsorbate(slab, 'O', height=2.0, position='fcc')
opt = BFGS(slab)
opt.run(fmax=0.05)
E_slab_ads = slab.get_potential_energy()
# 3. Calculate isolated adsorbate energy
from ase import Atoms
adsorbate = Atoms('O', positions=[(0, 0, 0)])
adsorbate.center(vacuum=10.0)
adsorbate.calc = EMT()
E_ads = adsorbate.get_potential_energy()
# 4. Adsorption energy
E_adsorption = E_slab_ads - E_slab - E_ads
print(f"Adsorption energy: {E_adsorption:.3f} eV")
from ase.build import bulk
from ase.eos import calculate_eos
import numpy as np
atoms = bulk('Cu', 'fcc', a=3.6)
atoms.calc = EMT()
# Method 1: Manual scan
cell_params = np.linspace(3.4, 3.8, 10)
energies = []
for a in cell_params:
atoms = bulk('Cu', 'fcc', a=a)
atoms.calc = EMT()
energies.append(atoms.get_potential_energy())
# Method 2: Using EOS
eos = calculate_eos(atoms, trajectory='eos.traj')
v, e, B = eos.fit() # volume, energy, bulk modulus
eos.plot('eos.png')
from ase.neb import NEB
from ase.optimize import BFGS
# Initial and final states
initial = read('initial.traj')
final = read('final.traj')
# Create intermediate images
images = [initial]
images += [initial.copy() for i in range(5)] # 5 intermediate images
images += [final]
# Interpolate
neb = NEB(images)
neb.interpolate()
# Set calculator for intermediate images
for image in images[1:-1]:
image.calc = EMT()
# Optimize
opt = BFGS(neb, trajectory='neb.traj')
opt.run(fmax=0.05)
# Get barrier
energies = [image.get_potential_energy() for image in images]
barrier = max(energies) - energies[0]
print(f"Activation barrier: {barrier:.3f} eV")
# Did calculation run?
print(atoms.calc.get_property('energy', atoms))
# Check forces
print(atoms.get_forces())
# Verify calculator parameters
print(atoms.calc.parameters)
# Quick plot without GUI
from ase.io import write
write('temp.png', atoms, rotation='10x,10y,10z')
# Check trajectory
traj = read('opt.traj', ':')
print(f"Number of steps: {len(traj)}")
for i, atoms in enumerate(traj):
print(f"Step {i}: E = {atoms.get_potential_energy():.3f} eV")
atoms.pbc = [True, True, False]When suggesting ASE solutions:
When helping with ASE: