Source code for clease.montecarlo.observers.diffraction_observer

import numpy as np
from clease.datastructures import SystemChanges
from .mc_observer import MCObserver


class DiffractionUpdater:
    """
    Utility class for all objects that require tracing of Fourier reflection.
    See docstring of DiffractionObserver for explanation of the arguments.
    This observer has to be executed on every MC step.
    """

    def __init__(self, atoms=None, k_vector=(), active_symbols=(), all_symbols=()):
        self.orig_symbols = [atom.symbol for atom in atoms]
        self.k_vector = k_vector
        self.N = len(atoms)
        self.k_dot_r = atoms.get_positions().dot(self.k_vector)
        self.indicator = {k: 0 for k in all_symbols}
        for symb in active_symbols:
            self.indicator[symb] = 1.0

        self.value = self.calculate_from_scratch(self.orig_symbols)
        self.prev_value = self.value

    def update(self, system_changes: SystemChanges):
        """Update the reflection value."""
        self.prev_value = self.value
        for change in system_changes:
            f_val = np.exp(1j * self.k_dot_r[change.index]) / self.N
            self.value += self.indicator[change.new_symb] * f_val
            self.value -= self.indicator[change.old_symb] * f_val

    def undo(self):
        """Undo the last update."""
        self.value = self.prev_value

    def reset(self):
        """Reset all values."""
        self.value = self.calculate_from_scratch(self.orig_symbols)
        self.prev_value = self.value

    def calculate_from_scratch(self, symbols):
        """Calculate the intensity from sctrach."""
        value = 0.0 + 1j * 0.0
        for i, symb in enumerate(symbols):
            value += self.indicator[symb] * np.exp(1j * self.k_dot_r[i])
        return value / len(symbols)


[docs]class DiffractionObserver(MCObserver): """ Trace the reflection intensity. Parameters: atoms: Atoms Atoms object used in Monte Carlo k_vector: list Fourier reflection to be traced active_symbols: list List of symbols that reflects all_symbols: list List of all symbols in the simulation name: str Name of the DiffractionObserver (users are given the freedom to set names because they can attach multiple DiffractionObserver instances) Example: Consider a system where Al, Mg and Si occupy FCC lattice sites. We want to trace the occurence of Mg layers that are separated by a distance 3*a where *a* is the lattice parameter. We further assume that the *y*-axis is normal to the planes we want to trace. In that case, we specify the variables as >>> from ase.build import bulk >>> import numpy as np >>> a = 4.05 >>> atoms = bulk('Al', crystalstructure='fcc', a=a) >>> k_vector = [0, 2.0*np.pi/(3*a), 0] >>> active_elements = ['Mg'] >>> all_symbols = ['Al', 'Mg', 'Si'] If we do not wish to distinguish Mi and Si (we do not distiguish Mg layer, Si layer or a mixture of the two) the `active_elements` is changed to >>> active_elements = ['Mg', 'Si'] """ def __init__( self, atoms=None, k_vector=(), active_symbols=(), all_symbols=(), name="reflection1", ): super().__init__() self.updater = DiffractionUpdater( atoms=atoms, k_vector=k_vector, active_symbols=active_symbols, all_symbols=all_symbols, ) self.avg = self.updater.value self.num_updates = 1 self.name = name def __call__(self, system_changes: SystemChanges): self.updater.update(system_changes) self.avg += self.updater.value
[docs] def get_averages(self): return {self.name: np.abs(self.avg / self.num_updates)}
[docs] def reset(self): self.updater.reset() self.avg = self.updater.value self.num_updates = 1
[docs] def interval_ok(self, interval): return interval == 1