"""
Contains classes and functions used to compute and store aspects related to convergence.
So far, the only procedure requring convergence is the static SCF cycle. More will
be added in future.
Classes
-------
* :class:`SCF` : holds the SCF convergence attributes and calculates them for the \
given cycle
"""
# standard libraries
# external libraries
import numpy as np
# internal modules
from . import mathtools
from . import config
[docs]
class SCF:
"""
Convergence attributes and functions related to SCF energy procedures.
Notes
-----
Contains the private attributes _energy, _potential and _density
"""
def __init__(self, xgrid, grid_type):
self._xgrid = xgrid
self._energy = np.zeros((2))
self._potential = np.zeros((2, config.spindims, config.grid_params["ngrid"]))
self._density = np.zeros((2, config.spindims, config.grid_params["ngrid"]))
self.grid_type = grid_type
[docs]
def check_conv(self, E_free, pot, dens, iscf):
"""
Compute and check the changes in energy, integrated density and integrated \
potential.
If the convergence tolerances are all simultaneously satisfied, the `complete` \
variable returns `True` as the SCF cycle is complete.
Parameters
----------
E_free : float
the total free energy
v_s : ndarray
the KS potential
dens : ndarray
the electronic density
iscf : int
the iteration number
Returns
-------
conv_vals : dict
Dictionary of convergence parameters as follows:
{
`conv_energy` : ``float``, `conv_rho` : ``ndarray``,
`conv_pot` : ``ndarray``, `complete` : ``bool``
}
"""
conv_vals = {}
# first update the energy, potential and density attributes
self._energy[0] = E_free
self._potential[0] = pot
self._density[0] = dens
# compute the change in energy
conv_vals["dE"] = abs((self._energy[0] - self._energy[1]) / self._energy[0])
# compute the change in potential
dv = np.abs(self._potential[0] - self._potential[1])
# compute the norm
norm_v = mathtools.int_sphere(
np.abs(self._potential[0]), self._xgrid, self.grid_type
)
conv_vals["dpot"] = (
mathtools.int_sphere(dv, self._xgrid, self.grid_type) / norm_v
)
# compute the change in density
dn = np.abs(self._density[0] - self._density[1])
# integrate over sphere to return a number
# add a small constant to avoid errors if no electrons in one spin channel
conv_vals["drho"] = mathtools.int_sphere(dn, self._xgrid, self.grid_type) / (
config.nele + 1e-3
)
# reset the energy, potential and density attributes
self._energy[1] = E_free
self._potential[1] = pot
self._density[1] = dens
# see if the convergence criteria are satisfied
conv_energy = False
conv_rho = False
conv_pot = False
conv_vals["complete"] = False
if iscf > 1:
if conv_vals["dE"] < config.conv_params["econv"]:
conv_energy = True
if np.all(conv_vals["drho"] < config.conv_params["nconv"]):
conv_rho = True
if np.all(conv_vals["dpot"] < config.conv_params["vconv"]):
conv_pot = True
if conv_energy and conv_rho and conv_pot:
conv_vals["complete"] = True
return conv_vals