Source code for mchammer.observers.binary_short_range_order_observer

from collections import namedtuple
from typing import Dict

import numpy as np

from ase import Atoms
from icet import ClusterSpace
from mchammer.observers import ClusterCountObserver
from mchammer.observers.base_observer import BaseObserver

ClusterCountInfo = namedtuple('ClusterCountInfo', ['counts', 'dc_tags'])


[docs] class BinaryShortRangeOrderObserver(BaseObserver): """ This class represents a short range order (SRO) observer for a binary system. Parameters ---------- cluster_space Cluster space used for initialization. structure Defines the lattice which the observer will work on. radius The maximum radius for the neigbhor shells considered. interval Observation interval. Defaults to ``None`` meaning that if the observer is used in a Monte Carlo simulations, then the :class:`Ensemble` object will determine the interval. Example ------- The following snippet illustrates how to use the short-range order (SRO) observer in a Monte Carlo simulation of a bulk supercell. Here, the parameters of the cluster expansion are set to emulate a simple Ising model in order to obtain an example that can be run without modification. In practice, one should of course use a proper cluster expansion:: >>> from ase.build import bulk >>> from icet import ClusterExpansion, ClusterSpace >>> from mchammer.calculators import ClusterExpansionCalculator >>> from mchammer.ensembles import CanonicalEnsemble >>> from mchammer.observers import BinaryShortRangeOrderObserver >>> # prepare cluster expansion >>> # the setup emulates a second nearest-neighbor (NN) Ising model >>> # (zerolet and singlet ECIs are zero; only first and second neighbor >>> # pairs are included) >>> prim = bulk('Au') >>> cs = ClusterSpace(prim, cutoffs=[4.3], chemical_symbols=['Ag', 'Au']) >>> ce = ClusterExpansion(cs, [0, 0, 0.1, -0.02]) >>> # prepare initial configuration >>> nAg = 10 >>> structure = prim.repeat(3) >>> structure.set_chemical_symbols(nAg * ['Ag'] + (len(structure) - nAg) * ['Au']) >>> # set up MC simulation >>> calc = ClusterExpansionCalculator(structure, ce) >>> mc = CanonicalEnsemble(structure=structure, calculator=calc, temperature=600, ... dc_filename='myrun_sro.dc') >>> # set up observer and attach it to the MC simulation >>> sro = BinaryShortRangeOrderObserver(cs, structure, interval=len(structure), radius=4.3) >>> mc.attach_observer(sro) >>> # run 1000 trial steps >>> mc.run(1000) After having run this snippet one can access the SRO parameters via the data container:: >>> print(mc.data_container.data) """ def __init__(self, cluster_space, structure: Atoms, radius: float, interval: int = None) -> None: super().__init__(interval=interval, return_type=dict, tag='BinaryShortRangeOrderObserver') self._structure = structure self._cluster_space = ClusterSpace( structure=cluster_space.primitive_structure, cutoffs=[radius], chemical_symbols=cluster_space.chemical_symbols) self._cluster_count_observer = ClusterCountObserver( cluster_space=self._cluster_space, structure=structure, interval=interval) self._sublattices = self._cluster_space.get_sublattices(structure) binary_sublattice_counts = 0 for symbols in self._sublattices.allowed_species: if len(symbols) == 2: binary_sublattice_counts += 1 self._symbols = sorted(symbols) elif len(symbols) > 2: raise ValueError('Cluster space has more than two allowed' ' species on a sublattice. ' 'Allowed species: {}'.format(symbols)) if binary_sublattice_counts != 1: raise ValueError('Number of binary sublattices must equal one,' ' not {}'.format(binary_sublattice_counts))
[docs] def get_observable(self, structure: Atoms) -> Dict[str, float]: """Returns the value of the property from a cluster expansion model for a given atomic configurations. Parameters ---------- structure Input atomic structure. """ df = self._cluster_count_observer.get_cluster_counts(structure) symbol_counts = self._get_atom_count(structure) conc_B = self._get_concentrations(structure)[self._symbols[0]] pair_orbit_indices = set( df.loc[df['order'] == 2]['orbit_index'].tolist()) N = symbol_counts[self._symbols[0]] + symbol_counts[self._symbols[1]] sro_parameters = {} for k, orbit_index in enumerate(sorted(pair_orbit_indices)): orbit_df = df.loc[df['orbit_index'] == orbit_index] A_B_pair_count = 0 total_count = 0 total_A_count = 0 for i, row in orbit_df.iterrows(): total_count += row.cluster_count if self._symbols[0] in row.occupation: total_A_count += row.cluster_count if self._symbols[0] in row.occupation and \ self._symbols[1] in row.occupation: A_B_pair_count += row.cluster_count key = 'sro_{}_{}'.format(self._symbols[0], k+1) Z_tot = symbol_counts[self._symbols[0]] * 2 * total_count / N if conc_B == 1 or Z_tot == 0: value = 0 else: value = 1 - A_B_pair_count/(Z_tot * (1-conc_B)) sro_parameters[key] = value return sro_parameters
def _get_concentrations(self, structure: Atoms) -> Dict[str, float]: """Returns concentrations for each species relative its sublattice. Parameters ---------- structure The configuration to be analyzed. """ occupation = np.array(structure.get_chemical_symbols()) concentrations = {} for sublattice in self._sublattices: if len(sublattice.chemical_symbols) == 1: continue for symbol in sublattice.chemical_symbols: symbol_count = occupation[sublattice.indices].tolist().count( symbol) concentration = symbol_count / len(sublattice.indices) concentrations[symbol] = concentration return concentrations def _get_atom_count(self, structure: Atoms) -> Dict[str, float]: """Returns atom counts for each species relative its sublattice. Parameters ---------- structure The configuration to be analyzed. """ occupation = np.array(structure.get_chemical_symbols()) counts = {} for sublattice in self._sublattices: if len(sublattice.chemical_symbols) == 1: continue for symbol in sublattice.chemical_symbols: symbol_count = occupation[sublattice.indices].tolist().count( symbol) counts[symbol] = symbol_count return counts