Source code for clease.montecarlo.observers.band_gap_observer

import logging

import ase
import numpy as np

from clease.datastructures import MCStep

from .mc_observer import MCObserver

logger = logging.getLogger(__name__)


[docs] class BandGapObserver(MCObserver): """ Observer that can be attached to a MC run, to track the band gap of a structure. It is possible to use the same calculator object as for the energy prediction if the ECIs are build with the same clusters leading to identical correlation functions. In the case of seperate clusters settings, an additional seperate calculator should be passed to the observer. Parameters: atoms : ase.Atoms (Pointer) Atoms object used for MC bandgap_eci : ECI coefficients for the band gap. separate_calc : A separate Clease optional calculator to use if the bandgap ECIs were fitted using different cluster settings than the energy calculator. If provided, correlation functions will be computed from this calculator instead of from atoms.calc. If None (default), uses the same calculator as the MC simulation, assuming both energy and bandgap use identical cluster definitions. verbose : bool If True, prints messages when new extreme band gaps are found. """ name = "BandGapObserver" def __init__( self, atoms: ase.Atoms, bandgap_eci: dict, seperate_calc=None, verbose: bool = False, ): super().__init__() self.atoms = atoms self.bandgap_eci = bandgap_eci # self.track_structure = track_structure self.verbose = verbose # Storage for band gap values self.bandgap_history = [] self.seperate_calc = seperate_calc
[docs] def reset(self) -> None: """Reset all tracked values.""" self.bandgap_history = []
@property def calc(self): """ Get the calculator object. """ return self.atoms.calc
[docs] def observe_step(self, mc_step: MCStep) -> None: """ Calculate and store the band gap for the current structure. Parameters ---------- mc_step : MCStep Information about the latest MC step """ # Get CF from seperate calculator or from the atoms object if self.seperate_calc is not None: # Match the separate calculator's atoms symbols # to match the current MC configuration self.seperate_calc.atoms.symbols[:] = self.atoms.symbols # force recalculation of CFs with the new symbols self.seperate_calc.update_cf(None) cf = self.seperate_calc.get_cf() else: cf = self.calc.get_cf() # Validate that all bandgap ECI clusters exist in the CF missing_clusters = set(self.bandgap_eci.keys()) - set(cf.keys()) if missing_clusters: raise ValueError( f"Bandgap ECIs contain clusters not in correlation functions: {missing_clusters}. " "Check that the cluster settings match between energy and bandgap fits." ) # Calculate bandgap using CIs. self.bandgap = sum( ci_value * cf[cluster_name] for cluster_name, ci_value in self.bandgap_eci.items() ) # Store in history self.bandgap_history.append(self.bandgap) # Print if verbose if self.verbose: msg = f"Current bandgap: {self.bandgap:.6f}" print(msg)
# Simple save function to txt file
[docs] def save(self, fname: str = "bandgap_evolution.txt") -> None: """ Save the band gap evolution to a txt file. """ np.savetxt(fname, self.bandgap_history, delimiter=",") logger.info("Band gap evolution data saved to %s.", fname)