Skip to content

Energy minimization

mdapy.simulation.minimize(system, calculator, *, max_force=1e-3, max_steps=1000, max_step=0.1, timestep=0.001, cell=None, pressure=0.0, max_stress=1e-2, save_every=None, callback=None) minimizes the energy over the positions, and optionally the box, with FIRE 2.0 (Guénolé et al. (2020)), the fast inertial relaxation engine of Bitzek et al. (2006), as LAMMPS min_style fire does with its default settings.

from mdapy.simulation import minimize

result = minimize(system, potential)  # the positions in a fixed box
result = minimize(system, potential, cell="full")  # and the box
result.system.box, result.stress  # the relaxed box and its stress in GPa
result = minimize(system, potential, cell="isotropic", pressure=10.0)
Name Type Default Unit Meaning
system System the starting configuration, with a mass or an element column; a movable column, (N, 3) bool as VASP's selective dynamics, keeps each atom still where it is False
calculator Calculator computes energies and forces, and the virial when the box relaxes
max_force float64 1e-3 eV/Å converged when every force is below it
max_steps int32 1000 largest number of steps
max_step float64 0.1 Å an atom moving at its current velocity goes at most this far along x, y or z in one step (LAMMPS dmax)
timestep float64 0.001 ps first time step of the dynamics (the LAMMPS timestep in metal units)
cell str None how the box may change, in the table below
pressure float64 0.0 GPa external hydrostatic pressure when the box relaxes
max_stress float64 1e-2 GPa when the box relaxes, converged when every relaxed component of the stress is this close to \(-p\) (−pressure on the diagonal, 0 off it)
save_every int32 None keeps the configuration every this many steps, with the first and the last, in frames
callback callable None called after each step as callback(step, system, energy, max_force); returning True stops the minimization

cell chooses how the box changes along its periodic directions; the atoms move with it, and the lattice vectors along directions that are not periodic stay as they are. At a pressure \(p\) the minimization minimizes the enthalpy \(E + pV\), so the stress of the relaxed box is \(-p\) on the diagonal.

cell The box Relaxed stress components
None does not change none
"isotropic" is scaled by one factor the mean of the diagonal
"axes" is scaled along each Cartesian axis by its own factor the diagonal
"full" takes any strain without rotation all

It returns a MinimizeResult:

Attribute Type Meaning
system System the last configuration, with forces and atomic_energies columns and energy, virial (eV) and stress (GPa) in info
converged bool whether every force fell below max_force and, when the box relaxes, every relaxed stress component came within max_stress of \(-p\)
steps int32 number of steps taken
energy float64 energy of the last configuration in eV
max_force float64 largest force of the last configuration in eV/Å
stress (3, 3) float64 or None stress of the last configuration in GPa, positive under tension; None when the calculator does not compute the virial
frames tuple of System or None with save_every, the kept configurations, with the columns and info of system and the step as timestep in info; None otherwise

virial and stress are in info, and stress is set, whenever the calculator computes the virial, also when the box does not change.

The steps on the positions are those of LAMMPS: the same time step control, mixing and half step back after an uphill step, with masses from the mass column or, when there is none, the standard atomic weights of the elements. Masses change the path, not the minimum; LAMMPS takes them from the data file or, for EAM, from the potential file. A box relaxation reaches the minimum of LAMMPS fix box/relax (which LAMMPS runs with min_style cg, not FIRE) by another path; LAMMPS keeps its box in restricted triclinic form, so the two boxes agree up to a rotation.

The kept frames show the minimization on its way, with the box and the stress of each step, and can be written as a trajectory:

from mdapy.io import write

result = minimize(system, potential, cell="full", save_every=10)
write("relaxation.xyz", result.frames)

A callback follows the minimization step by step. Copper with every atom displaced at random by about 0.1 Å, relaxed to forces below 1e-4 eV/Å:

import numpy as np
from mdapy.build import build_crystal
from mdapy.visualization import plot

crystal = build_crystal("fcc", 3.615, elements="Cu", repeat=(4, 4, 4))
shake = np.random.default_rng(0).normal(0, 0.1, (len(crystal), 3))
shaken = crystal.with_columns(positions=crystal["positions"] + shake)

steps = []  # step, energy and largest force after each step


def record(step, system, energy, max_force):
    steps.append((step, energy, max_force))


result = minimize(shaken, potential, max_force=1e-4, callback=record)
step, energy, force = np.array(steps).T

with plot.style():
    fig, (top, bottom) = plot.figure("single", 100, nrows=2, sharex=True)
    top.semilogy(step, energy - result.energy)
    top.set_ylabel("Energy above the minimum (eV)")
    bottom.semilogy(step, force)
    bottom.set_ylabel("Largest force (eV/Å)")
    bottom.set_xlabel("Step")
    plot.save(fig, "images/minimize", formats=("png",))

FIRE minimization of shaken copper

The energy and the largest force fall by orders of magnitude in 121 steps. Each kink is an uphill step: FIRE steps half a step back, stops the atoms and starts again with a shorter time step.

minimize raises InvalidParameter for a non-positive max_force, max_step, timestep, max_stress or save_every, a negative max_steps, an unknown cell, or a calculator that does not compute energies and forces, and the virial when the box relaxes, and MissingProperty for a system with neither masses nor elements.