Source code for mchammer.calculators.constituent_strain_calculator

from icet.tools import ConstituentStrain
from icet import ClusterExpansion
from mchammer.calculators import ClusterExpansionCalculator
import numpy as np


[docs] class ConstituentStrainCalculator(ClusterExpansionCalculator): """ Calculator for a cluster expansion combined with constituent strain. The property this calculator evaluates is the sum of the cluster expansion energy and the constituent strain energy, both for the whole supercell. The constituent strain energy is evaluated by the :class:`ConstituentStrain <icet.tools.ConstituentStrain>` object, which describes the strain energy of a coherent structure in reciprocal space. The section on :ref:`constituent strain <constituent_strain_example>` describes the method and shows the calculator in a Monte Carlo simulation. The :class:`ConstituentStrain <icet.tools.ConstituentStrain>` object stores one structure factor per k-point and reads it as the value before a change instead of recomputing it. The calculator therefore rebuilds the structure factors in :func:`set_occupations` and advances them in :func:`accept_change`. Only changes of a single site can be evaluated, so the calculator works with ensembles that flip one site per trial step, such as :class:`SemiGrandCanonicalEnsemble <mchammer.ensembles.SemiGrandCanonicalEnsemble>` and :class:`VCSGCEnsemble <mchammer.ensembles.VCSGCEnsemble>`. Parameters ---------- constituent_strain Object that defines the strain energy of the system. Its supercell is the structure the calculator is set up for, so it has to be the one the Monte Carlo simulation samples. cluster_expansion Cluster expansion from which to build the calculator. name Human-readable identifier for this calculator. scaling Scaling factor applied to the property value predicted by the cluster expansion. By default the number of sites in the supercell. """ def __init__(self, constituent_strain: ConstituentStrain, cluster_expansion: ClusterExpansion, name: str = 'Constituent Strain Calculator', scaling: float | None = None) -> None: self.constituent_strain = constituent_strain super().__init__(structure=constituent_strain.supercell, cluster_expansion=cluster_expansion, name=name, scaling=scaling)
[docs] def set_occupations(self, occupations: list[int]) -> None: """ Sets the configuration this calculator describes, both for the underlying cluster expansion calculator and for the :class:`ConstituentStrain <icet.tools.ConstituentStrain>` object. Parameters ---------- occupations The entire occupation vector by atomic number. """ super().set_occupations(occupations) # The structure factor stored for each k-point describes one # configuration, and the change evaluation reads it as the value # before the change rather than recomputing it from the occupations # it is given. Moving to a configuration that is not reachable from # the stored one through accepted changes therefore has to rebuild it, # which a total evaluation does for every k-point. self.constituent_strain.get_constituent_strain(np.asarray(occupations))
[docs] def calculate_total(self, *, occupations: np.ndarray) -> float: """ Returns the total property value of a configuration, which is the sum of the cluster expansion energy and the constituent strain energy of the whole supercell. The configuration is given in full and is not adopted, so the configuration the calculator holds is left where it is. Parameters ---------- occupations The entire occupation vector by atomic number. """ e = super().calculate_total(occupations=occupations) # The structure factors are left where they are, since a total # evaluation scores the occupations it is given and must not move the # configuration the next change is evaluated against. e += len(occupations) * \ self.constituent_strain.get_constituent_strain( occupations, update_structure_factors=False) return e
[docs] def calculate_change(self, *, sites: list[int], current_occupations: list[int], new_site_occupations: list[int]) -> float: """ Returns the change of the property caused by changing the occupation of the site in :attr:`sites`. Parameters ---------- sites Index of the site whose occupation changes, as a list of length one. current_occupations The entire occupation vector by atomic number before the change. new_site_occupations Atomic number after the change, as a list of length one. Raises ------ NotImplementedError If more than one site changes. """ if len(new_site_occupations) > 1: raise NotImplementedError('Only single flips are currently allowed in ' 'conjunction with the constituent strain calculator.') e = super().calculate_change(sites=sites, current_occupations=current_occupations, new_site_occupations=new_site_occupations) de_cs = self.constituent_strain.get_constituent_strain_change(current_occupations, sites[0]) e += len(current_occupations) * de_cs return e
[docs] def accept_change(self, *, sites: list[int] | None = None, species: list[int] | None = None) -> None: """ Advances the configuration held by the underlying cluster expansion calculator and informs the :class:`ConstituentStrain <icet.tools.ConstituentStrain>` object that the most recent change was accepted, so that it stores the new structure factors. Parameters ---------- sites Indices of the sites whose occupations changed. species New occupations by atomic number on those sites. """ super().accept_change(sites=sites, species=species) # An empty change advances nothing, so the structure factors must not # advance either. Forwarding it would commit the candidate the last # change evaluation staged, which is the trial that was rejected. if len(sites) == 0: return self.constituent_strain.accept_change()