Coverage for /builds/kinetik161/ase/ase/optimize/cellawarebfgs.py: 98.57%
70 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1from typing import IO, Optional, Union
2import numpy as np
3import time
4from ase.optimize import BFGS
5from ase.optimize.optimize import Dynamics
6from ase import Atoms
7from ase.geometry import cell_to_cellpar
8from ase.units import GPa
11def calculate_isotropic_elasticity_tensor(bulk_modulus, poisson_ratio,
12 suppress_rotation=0):
13 """
14 Parameters:
15 bulk_modulus Bulk Modulus of the isotropic system used to set up the
16 Hessian (in ASE units (eV/Å^3)).
18 poisson_ratio Poisson ratio of the isotropic system used to set up the
19 initial Hessian (unitless, between -1 and 0.5).
21 suppress_rotation The rank-2 matrix C_ijkl.reshape((9,9)) has by
22 default 6 non-zero eigenvalues, because energy is
23 invariant to orthonormal rotations of the cell
24 vector. This serves as a bad initial Hessian due to 3
25 zero eigenvalues. Suppress rotation sets a value for
26 those zero eigenvalues.
28 Returns C_ijkl
29 """
31 # https://scienceworld.wolfram.com/physics/LameConstants.html
32 _lambda = 3 * bulk_modulus * poisson_ratio / (1 + 1 * poisson_ratio)
33 _mu = _lambda * (1 - 2 * poisson_ratio) / (2 * poisson_ratio)
35 # https://en.wikipedia.org/wiki/Elasticity_tensor
36 g_ij = np.eye(3)
38 # Construct 4th rank Elasticity tensor for isotropic systems
39 C_ijkl = _lambda * np.einsum('ij,kl->ijkl', g_ij, g_ij)
40 C_ijkl += _mu * (np.einsum('ik,jl->ijkl', g_ij, g_ij) +
41 np.einsum('il,kj->ijkl', g_ij, g_ij))
43 # Supplement the tensor with suppression of pure rotations that are right
44 # now 0 eigenvalues.
45 # Loop over all basis vectors of skew symmetric real matrix
46 for i, j in ((0, 1), (0, 2), (1, 2)):
47 Q = np.zeros((3, 3))
48 Q[i, j], Q[j, i] = 1, -1
49 C_ijkl += (np.einsum('ij,kl->ijkl', Q, Q)
50 * suppress_rotation / 2)
52 return C_ijkl
55class CellAwareBFGS(BFGS):
56 def __init__(
57 self,
58 atoms: Atoms,
59 restart: Optional[str] = None,
60 logfile: Union[IO, str] = '-',
61 trajectory: Optional[str] = None,
62 maxstep: Optional[float] = None,
63 master: Optional[bool] = None,
64 bulk_modulus: Optional[float] = 145 * GPa,
65 poisson_ratio: Optional[float] = 0.3,
66 alpha: Optional[float] = None,
67 long_output: Optional[bool] = False,
68 ):
69 self.bulk_modulus = bulk_modulus
70 self.poisson_ratio = poisson_ratio
71 self.long_output = long_output
72 BFGS.__init__(self, atoms=atoms, restart=restart, logfile=logfile,
73 trajectory=trajectory, maxstep=maxstep, master=master,
74 alpha=alpha)
75 assert not isinstance(atoms, Atoms)
76 if hasattr(atoms, 'exp_cell_factor'):
77 assert atoms.exp_cell_factor == 1.0
79 def initialize(self):
80 BFGS.initialize(self)
81 C_ijkl = calculate_isotropic_elasticity_tensor(
82 self.bulk_modulus,
83 self.poisson_ratio,
84 suppress_rotation=self.alpha)
85 cell_H = self.H0[-9:, -9:]
86 ind = np.where(self.atoms.mask.ravel() != 0)[0]
87 cell_H[np.ix_(ind, ind)] = C_ijkl.reshape((9, 9))[
88 np.ix_(ind, ind)] * self.atoms.atoms.cell.volume
90 def converged(self, forces=None):
91 if forces is None:
92 forces = self.atoms.atoms.get_forces()
93 stress = self.atoms.atoms.get_stress()
94 return np.max(np.sum(forces**2, axis=1))**0.5 < self.fmax and \
95 np.max(np.abs(stress)) < self.smax
97 def run(self, fmax=0.05, smax=0.005, steps=None):
98 """ call Dynamics.run and keep track of fmax"""
99 self.fmax = fmax
100 self.smax = smax
101 if steps is not None:
102 self.max_steps = steps
103 return Dynamics.run(self)
105 def log(self, forces=None):
106 if forces is None:
107 forces = self.atoms.atoms.get_forces()
108 fmax = (forces ** 2).sum(axis=1).max() ** 0.5
109 e = self.optimizable.get_potential_energy()
110 T = time.localtime()
111 smax = abs(self.atoms.atoms.get_stress()).max()
112 volume = self.atoms.atoms.cell.volume
113 if self.logfile is not None:
114 name = self.__class__.__name__
115 if self.nsteps == 0:
116 args = (" " * len(name),
117 "Step", "Time", "Energy", "fmax", "smax", "volume")
118 msg = "\n%s %4s %8s %15s %15s %15s %15s" % args
119 if self.long_output:
120 msg += ("%8s %8s %8s %8s %8s %8s" %
121 ('A', 'B', 'C', 'α', 'β', 'γ'))
122 msg += '\n'
123 self.logfile.write(msg)
125 ast = ''
126 args = (name, self.nsteps, T[3], T[4], T[5], e, ast, fmax, smax,
127 volume)
128 msg = ("%s: %3d %02d:%02d:%02d %15.6f%1s %15.6f %15.6f %15.6f" %
129 args)
130 if self.long_output:
131 msg += ("%8.3f %8.3f %8.3f %8.3f %8.3f %8.3f" %
132 tuple(cell_to_cellpar(self.atoms.atoms.cell)))
133 msg += '\n'
134 self.logfile.write(msg)
136 self.logfile.flush()