Source code for ase.optimize.cellawarebfgs

# fmt: off

import time
from typing import IO, Optional, Union

import numpy as np

from ase import Atoms
from ase.geometry import cell_to_cellpar
from ase.optimize import BFGS
from ase.optimize.optimize import Dynamics
from ase.units import GPa


def calculate_isotropic_elasticity_tensor(bulk_modulus, poisson_ratio,
                                          suppress_rotation=0):
    """
    Parameters:
        bulk_modulus Bulk Modulus of the isotropic system used to set up the
                     Hessian (in ASE units (eV/Å^3)).

        poisson_ratio Poisson ratio of the isotropic system used to set up the
                      initial Hessian (unitless, between -1 and 0.5).

        suppress_rotation The rank-2 matrix C_ijkl.reshape((9,9)) has by
                          default 6 non-zero eigenvalues, because energy is
                          invariant to orthonormal rotations of the cell
                          vector. This serves as a bad initial Hessian due to 3
                          zero eigenvalues. Suppress rotation sets a value for
                          those zero eigenvalues.

           Returns C_ijkl
    """

    # https://scienceworld.wolfram.com/physics/LameConstants.html
    _lambda = 3 * bulk_modulus * poisson_ratio / (1 + 1 * poisson_ratio)
    _mu = _lambda * (1 - 2 * poisson_ratio) / (2 * poisson_ratio)

    # https://en.wikipedia.org/wiki/Elasticity_tensor
    g_ij = np.eye(3)

    # Construct 4th rank Elasticity tensor for isotropic systems
    C_ijkl = _lambda * np.einsum('ij,kl->ijkl', g_ij, g_ij)
    C_ijkl += _mu * (np.einsum('ik,jl->ijkl', g_ij, g_ij) +
                     np.einsum('il,kj->ijkl', g_ij, g_ij))

    # Supplement the tensor with suppression of pure rotations that are right
    # now 0 eigenvalues.
    # Loop over all basis vectors of skew symmetric real matrix
    for i, j in ((0, 1), (0, 2), (1, 2)):
        Q = np.zeros((3, 3))
        Q[i, j], Q[j, i] = 1, -1
        C_ijkl += (np.einsum('ij,kl->ijkl', Q, Q)
                   * suppress_rotation / 2)

    return C_ijkl


[docs] class CellAwareBFGS(BFGS): def __init__( self, atoms: Atoms, restart: Optional[str] = None, logfile: Union[IO, str] = '-', trajectory: Optional[str] = None, append_trajectory: bool = False, maxstep: Optional[float] = None, bulk_modulus: Optional[float] = 145 * GPa, poisson_ratio: Optional[float] = 0.3, alpha: Optional[float] = None, long_output: Optional[bool] = False, **kwargs, ): self.bulk_modulus = bulk_modulus self.poisson_ratio = poisson_ratio self.long_output = long_output super().__init__( atoms=atoms, restart=restart, logfile=logfile, trajectory=trajectory, maxstep=maxstep, alpha=alpha, append_trajectory=append_trajectory, **kwargs) assert not isinstance(atoms, Atoms) if hasattr(atoms, 'exp_cell_factor'): assert atoms.exp_cell_factor == 1.0 def initialize(self): super().initialize() C_ijkl = calculate_isotropic_elasticity_tensor( self.bulk_modulus, self.poisson_ratio, suppress_rotation=self.alpha) cell_H = self.H0[-9:, -9:] ind = np.where(self.atoms.mask.ravel() != 0)[0] cell_H[np.ix_(ind, ind)] = C_ijkl.reshape((9, 9))[ np.ix_(ind, ind)] * self.atoms.atoms.cell.volume def converged(self, gradient): # XXX currently ignoring gradient forces = self.atoms.atoms.get_forces() stress = self.atoms.atoms.get_stress(voigt=False) * self.atoms.mask return np.max(np.sum(forces**2, axis=1))**0.5 < self.fmax and \ np.max(np.abs(stress)) < self.smax def run(self, fmax=0.05, smax=0.005, steps=None): """ call Dynamics.run and keep track of fmax""" self.fmax = fmax self.smax = smax if steps is not None: return Dynamics.run(self, steps=steps) return Dynamics.run(self) def log(self, gradient): # XXX ignoring gradient forces = self.atoms.atoms.get_forces() fmax = (forces ** 2).sum(axis=1).max() ** 0.5 e = self.optimizable.get_value() T = time.localtime() smax = abs(self.atoms.atoms.get_stress(voigt=False) * self.atoms.mask).max() volume = self.atoms.atoms.cell.volume if self.logfile is not None: name = self.__class__.__name__ if self.nsteps == 0: args = (" " * len(name), "Step", "Time", "Energy", "fmax", "smax", "volume") msg = "\n%s %4s %8s %15s %15s %15s %15s" % args if self.long_output: msg += ("%8s %8s %8s %8s %8s %8s" % ('A', 'B', 'C', 'α', 'β', 'γ')) msg += '\n' self.logfile.write(msg) ast = '' args = (name, self.nsteps, T[3], T[4], T[5], e, ast, fmax, smax, volume) msg = ("%s: %3d %02d:%02d:%02d %15.6f%1s %15.6f %15.6f %15.6f" % args) if self.long_output: msg += ("%8.3f %8.3f %8.3f %8.3f %8.3f %8.3f" % tuple(cell_to_cellpar(self.atoms.atoms.cell))) msg += '\n' self.logfile.write(msg)