Source code for nbed.localizers.occupied.base
"""Base Localizer Class."""
import logging
from abc import ABC, abstractmethod
import numpy as np
from pyscf import scf # type:ignore
from nbed.localizers.system import RestrictedLS, UnrestrictedLS
from ..system import LocalizedSystem
logger = logging.getLogger(__name__)
[docs]
class OccupiedLocalizer(ABC):
"""Object used to localise molecular orbitals (MOs) using different localization schemes.
Running localization returns active and environment systems.
Note:
The major improvement of IBOs over PM orbitals is that they are based on IAO charges instead of the erratic
Mulliken charges. As a result, IBOs are always well-defined. (Ref: J. Chem. Theory Comput. 2013, 9, 4834−4843)
Args:
global_scf (gto.Mole): PySCF molecule object
n_active_atoms (int): Number of active atoms
Methods:
run: Main function to run localization.
"""
def __init__(
self,
global_scf: scf.hf.SCF,
n_active_atoms: int,
n_mo_overwrite: tuple[int | None, int | None] | None = None,
):
"""Initialise class."""
logger.debug("Initialising OccupiedLocalizerTypes.")
if global_scf.mo_coeff is None:
logger.debug("SCF method not initialised, running now...")
global_scf.run()
logger.debug("SCF method initialised.")
self.n_mo_overwrite = (None, None) if n_mo_overwrite is None else n_mo_overwrite
self._global_scf = global_scf
self._n_active_atoms = n_active_atoms
match global_scf.mo_coeff.ndim: # type: ignore
case 2:
self.spinless = True
case 3:
self.spinless = False
case _:
raise ValueError("SCF C matrix shape not valid.")
logger.debug(f"Global scf: {type(global_scf)}")
[docs]
def localize(
self,
) -> LocalizedSystem:
"""Localise orbitals using SPADE.
Returns:
LocalizedSystem: A dataclass describing the localization.
"""
localized_system: LocalizedSystem
if self.spinless:
logger.debug("Running SPADE for only one spin.")
localized_system = self._localize_spin(
self._global_scf.mo_coeff, # type:ignore
self._global_scf.mo_occ, # type:ignore
self.n_mo_overwrite[0],
)
localized_system.dm_active *= 2
localized_system.dm_enviro *= 2
localized_system.dm_loc_occ *= 2
else:
alpha = self._localize_spin(
self._global_scf.mo_coeff[0], # type:ignore
self._global_scf.mo_occ[0], # type:ignore
self.n_mo_overwrite[0],
)
beta = self._localize_spin(
self._global_scf.mo_coeff[1], # type:ignore
self._global_scf.mo_occ[1], # type:ignore
self.n_mo_overwrite[1],
)
localized_system = UnrestrictedLS.from_spin_components(alpha, beta)
# to ensure the same number of alpha and beta orbitals are included
# use the sum of occupancies
logger.debug("Localization complete.")
return localized_system
@abstractmethod
def _localize_spin(
self,
c_matrix: np.ndarray,
occupancy: np.ndarray,
n_mo_overwrite: int | None = None,
) -> RestrictedLS:
"""Localize orbitals of one spin.
Args:
c_matrix (np.ndarray): Unlocalized C matrix of occupied orbitals.
occupancy (np.ndarray): Occupancy of orbitals.
n_mo_overwrite (int | None): Overwrite the number of active molecular orbitals.
Returns:
np.ndarray: Localized C matrix of occupied orbitals.
"""
pass