Source code for icet.tools.variable_transformation

from itertools import combinations, permutations
from typing import List

import numpy as np
from ase import Atoms
from icet.core.orbit import Orbit
from icet.core.orbit_list import OrbitList
from icet.core.lattice_site import LatticeSite


def _is_site_group_in_orbit(orbit: Orbit, site_group: List[LatticeSite]) -> bool:
    """Checks if a list of sites is found among the clusters in an orbit.
    The number of sites must match the order of the orbit.

    Parameters
    ----------
    orbit
        Orbit.
    site_group
        Sites to be searched for.
    """

    # Ensure that the number of sites matches the order of the orbit
    if len(site_group) != orbit.order:
        return False

    # Check if the set of lattice sites is found among the equivalent sites
    if set(site_group) in [set(cl.lattice_sites) for cl in orbit.clusters]:
        return True

    # Go through all clusters
    site_indices = [site.index for site in site_group]
    for cluster in orbit.clusters:
        cluster_site_indices = [s.index for s in cluster.lattice_sites]

        # Skip if the site indices do not match
        if set(site_indices) != set(cluster_site_indices):
            continue

        # Loop over all permutations of the lattice sites in cluster
        for cluster_site_group in permutations(cluster.lattice_sites):

            # Skip all cases that include pairs of sites with different site indices
            if any(site1.index != site2.index
                   for site1, site2 in zip(site_group, cluster_site_group)):
                continue

            # If the relative offsets for all pairs of sites match, the two
            # clusters are equivalent
            relative_offsets = [site1.unitcell_offset - site2.unitcell_offset
                                for site1, site2 in zip(site_group, cluster_site_group)]
            if all(np.array_equal(ro, relative_offsets[0]) for ro in relative_offsets):
                return True
    return False


[docs] def get_transformation_matrix(structure: Atoms, full_orbit_list: OrbitList) -> np.ndarray: r""" Determines the matrix that transforms the cluster functions in the form of spin variables, :math:`\sigma_i\in\{-1,1\}`, to their binary equivalents, :math:`x_i\in\{0,1\}`. The form is obtained by performing the substitution (:math:`\sigma_i=1-2x_i`) in the cluster expansion expression of the predicted property (commonly the energy). Parameters ---------- structure Atomic configuration. full_orbit_list Full orbit list. """ # Go through all clusters associated with each active orbit and # determine its contribution to each orbit orbit_indices = range(len(full_orbit_list)) transformation = np.zeros((len(orbit_indices) + 1, len(orbit_indices) + 1)) transformation[0, 0] = 1.0 for i, orb_index in enumerate(orbit_indices, 1): orbit = full_orbit_list.get_orbit(orb_index) repr_sites = orbit.representative_cluster.lattice_sites # add contributions to the lower order orbits to which the # subclusters belong for sub_order in range(orbit.order + 1): n_terms_target = len(list(combinations(orbit.representative_cluster.lattice_sites, sub_order))) n_terms_actual = 0 if sub_order == 0: transformation[0, i] += 1.0 n_terms_actual += 1 if sub_order == orbit.order: transformation[i, i] += (-2.0) ** (sub_order) n_terms_actual += 1 else: comb_sub_sites = combinations(repr_sites, sub_order) for sub_sites in comb_sub_sites: for j, sub_index in enumerate(orbit_indices, 1): sub_orbit = full_orbit_list.get_orbit(sub_index) if sub_orbit.order != sub_order: continue if _is_site_group_in_orbit(sub_orbit, sub_sites): transformation[j, i] += (-2.0) ** (sub_order) n_terms_actual += 1 # If the number of contributions does not match the number of subclusters, # this orbit list is incompatible with the ground state finder # of subclusters if n_terms_actual != n_terms_target: raise ValueError('At least one cluster had subclusters that were not included' ' in the cluster space. This is typically caused by cutoffs' ' that are longer for a higher-order orbit than lower-order one' ' (such as 8 Angstrom for triplets and 6 Angstrom for pairs).' ' Please use a different cluster space for the ground state ' ' finder.') return transformation
[docs] def transform_parameters(structure: Atoms, full_orbit_list: OrbitList, parameters: np.ndarray) -> np.ndarray: r""" Transforms the list of parameters, obtained using cluster functions in the form of of spin variables, :math:`\sigma_i\in\{-1,1\}`, to their equivalents for the case of binary variables, :math:`x_i\in\{0,1\}`. Parameters ---------- structure Atomic configuration. full_orbit_list Full orbit list. parameters Parameter vector (spin variables). """ A = get_transformation_matrix(structure, full_orbit_list) return np.dot(A, parameters)