Source code for icet.core.local_cluster_expansion

"""
This module provides the LocalClusterExpansion class.
"""

import json
from typing import Any

import numpy as np

from ase import Atoms

from icet.core.base_cluster_expansion import BaseClusterExpansion
from icet.core.local_cluster_space import LocalClusterSpace
from icet.input_output.repr_tools import (html_representation,
                                          text_representation)


def encode_numpy(value):
    """Converts numpy values to plain Python for JSON serialization.

    Metadata commonly comes from a fitting summary, for example the one of
    the trainstation CrossValidationEstimator, whose values carry numpy
    types.
    """
    if isinstance(value, np.integer):
        return int(value)
    if isinstance(value, np.floating):
        return float(value)
    if isinstance(value, np.ndarray):
        return value.tolist()
    raise TypeError(f'{type(value)} is not JSON serializable')


[docs] class LocalClusterExpansion(BaseClusterExpansion): """A local cluster expansion pairs a local cluster space with a set of parameters, where the latter is commonly obtained by fitting to locally defined energy differences such as kinetically resolved activation barriers. Instances of this class predict the local property for an event instance in a supercell. Parameters ---------- local_cluster_space Local cluster space that defines the basis of the expansion. parameters Parameter vector, one element per element of the space. metadata User-defined metadata to be stored together with the expansion. Raises ------ ValueError If :attr:`local_cluster_space` and :attr:`parameters` differ in length. """ _space_name = 'local_cluster_space' def __init__(self, local_cluster_space: LocalClusterSpace, parameters: np.ndarray, metadata: dict | None = None) -> None: self._local_cluster_space = local_cluster_space super().__init__(local_cluster_space, parameters, metadata) @property def _space(self) -> LocalClusterSpace: """The space that defines the basis of the expansion.""" return self._local_cluster_space @property def local_cluster_space(self) -> LocalClusterSpace: """Local cluster space on which the expansion is based.""" return self._local_cluster_space
[docs] def predict(self, structure: Atoms, event_sites: tuple) -> float: """Returns the local property predicted for an event instance. Parameters ---------- structure Supercell whose occupations define the environment. event_sites Indices of the anchor sites in the supercell. """ vector = self._local_cluster_space.get_local_cluster_vector( structure, event_sites) return float(np.dot(vector, self.parameters))
def _get_rows(self) -> list[tuple[str, Any]]: """Returns the label and value pairs that summarize this expansion, which are those of the space with the number of nonzero parameters added to the number of parameters.""" nonzero = int(np.count_nonzero(self._parameters)) rows = [] for label, value in self._local_cluster_space._get_rows(): if label == 'total number of parameters': label, value = (f'{label} (nonzero)', f'{value} ({nonzero})') rows.append((label, value)) return rows def __str__(self) -> str: """ String representation. """ return text_representation(title='Local Cluster Expansion', rows=self._get_rows(), width=60, label_width=36) def _repr_html_(self) -> str: """ HTML representation. Used, e.g., in jupyter notebooks. """ return html_representation(title='Local Cluster Expansion', rows=self._get_rows())
[docs] def write(self, filename: str) -> None: """Writes the local cluster expansion to a file. Metadata is serialized as JSON, with numpy values converted to plain numbers and lists. Parameters ---------- filename Name of the file to which to write. """ data = {'type': type(self).__name__, 'local_cluster_space': self._local_cluster_space.to_dict(), 'parameters': [float(p) for p in self._parameters], 'metadata': self._metadata} # Serialization happens before the file is opened, so a metadata # value that is not serializable raises without truncating a # previously saved expansion. payload = json.dumps(data, ensure_ascii=False, default=encode_numpy) with open(filename, 'w', encoding='utf-8') as handle: handle.write(payload)
[docs] @classmethod def read(cls, filename: str) -> 'LocalClusterExpansion': """Reads a local cluster expansion from a file. Parameters ---------- filename Name of the file from which to read. """ with open(filename, encoding='utf-8') as handle: data = json.load(handle) if data.get('type') != cls.__name__: raise ValueError(f'{filename} does not contain a {cls.__name__}') expansion = cls(LocalClusterSpace.from_dict(data['local_cluster_space']), np.array(data['parameters'])) expansion._metadata = data['metadata'] return expansion