Source code for pymatgen.analysis.chemenv.utils.func_utils

# coding: utf-8
# Copyright (c) Pymatgen Development Team.
# Distributed under the terms of the MIT License.


"""
This module contains some utility functions and classes that are used in the chemenv package.
"""

__author__ = "David Waroquiers"
__copyright__ = "Copyright 2012, The Materials Project"
__credits__ = "Geoffroy Hautier"
__version__ = "2.0"
__maintainer__ = "David Waroquiers"
__email__ = "david.waroquiers@gmail.com"
__date__ = "Feb 20, 2016"

from typing import Dict

import numpy as np
from pymatgen.analysis.chemenv.utils.math_utils import power2_inverse_decreasing, power2_decreasing_exp
from pymatgen.analysis.chemenv.utils.math_utils import smoothstep, smootherstep
from pymatgen.analysis.chemenv.utils.math_utils import power2_inverse_power2_decreasing


[docs]class AbstractRatioFunction: ALLOWED_FUNCTIONS = {} # type: Dict[str, list] def __init__(self, function, options_dict=None): if function not in self.ALLOWED_FUNCTIONS: raise ValueError('Function "{}" is not allowed in RatioFunction of ' 'type "{}"'.format(function, self.__class__.__name__)) self.eval = object.__getattribute__(self, function) self.function = function self.setup_parameters(options_dict=options_dict)
[docs] def setup_parameters(self, options_dict): function_options = self.ALLOWED_FUNCTIONS[self.function] if len(function_options) > 0: # Check if there are missing options if options_dict is None: missing_options = True else: missing_options = False for op in function_options: if op not in options_dict: missing_options = True break # If there are missing options, raise an error if missing_options: if len(function_options) == 1: opts = 'Option "{}"'.format(function_options[0]) else: opts1 = ', '.join(['"{}"'.format(op) for op in function_options[:-1]]) opts = 'Options {}'.format(' and '.join([opts1, '"{}"'.format(function_options[-1])])) if options_dict is None or len(options_dict) == 0: missing = 'no option was provided.' else: optgiven = list(options_dict.keys()) if len(options_dict) == 1: missing = 'only "{}" was provided.'.format(optgiven[0]) else: missing1 = ', '.join(['"{}"'.format(miss) for miss in optgiven[:-1]]) missing = 'only {} were provided.'.format(' and '.join([missing1, '"{}"'.format(optgiven[-1])])) raise ValueError('{} should be provided for function "{}" in RatioFunction of ' 'type "{}" while {}'.format(opts, self.function, self.__class__.__name__, missing)) # Setup the options and raise an error if a wrong option is provided for key, val in options_dict.items(): if key not in function_options: raise ValueError('Option "{}" not allowed for function "{}" in RatioFunction of ' 'type "{}"'.format(key, self.function, self.__class__.__name__)) self.__setattr__(key, val)
[docs] def evaluate(self, value): return self.eval(value)
[docs] @classmethod def from_dict(cls, dd): return cls(function=dd['function'], options_dict=dd['options'])
[docs]class RatioFunction(AbstractRatioFunction): ALLOWED_FUNCTIONS = {'power2_decreasing_exp': ['max', 'alpha'], 'smoothstep': ['lower', 'upper'], 'smootherstep': ['lower', 'upper'], 'inverse_smoothstep': ['lower', 'upper'], 'inverse_smootherstep': ['lower', 'upper'], 'power2_inverse_decreasing': ['max'], 'power2_inverse_power2_decreasing': ['max'] }
[docs] def power2_decreasing_exp(self, vals): return power2_decreasing_exp(vals, edges=[0.0, self.__dict__['max']], alpha=self.__dict__['alpha'])
[docs] def smootherstep(self, vals): return smootherstep(vals, edges=[self.__dict__['lower'], self.__dict__['upper']])
[docs] def smoothstep(self, vals): return smoothstep(vals, edges=[self.__dict__['lower'], self.__dict__['upper']])
[docs] def inverse_smootherstep(self, vals): return smootherstep(vals, edges=[self.__dict__['lower'], self.__dict__['upper']], inverse=True)
[docs] def inverse_smoothstep(self, vals): return smoothstep(vals, edges=[self.__dict__['lower'], self.__dict__['upper']], inverse=True)
[docs] def power2_inverse_decreasing(self, vals): return power2_inverse_decreasing(vals, edges=[0.0, self.__dict__['max']])
[docs] def power2_inverse_power2_decreasing(self, vals): return power2_inverse_power2_decreasing(vals, edges=[0.0, self.__dict__['max']])
[docs]class CSMFiniteRatioFunction(AbstractRatioFunction): ALLOWED_FUNCTIONS = {'power2_decreasing_exp': ['max_csm', 'alpha'], 'smoothstep': ['lower_csm', 'upper_csm'], 'smootherstep': ['lower_csm', 'upper_csm'] }
[docs] def power2_decreasing_exp(self, vals): return power2_decreasing_exp(vals, edges=[0.0, self.__dict__['max_csm']], alpha=self.__dict__['alpha'])
[docs] def smootherstep(self, vals): return smootherstep(vals, edges=[self.__dict__['lower_csm'], self.__dict__['upper_csm']], inverse=True)
[docs] def smoothstep(self, vals): return smootherstep(vals, edges=[self.__dict__['lower_csm'], self.__dict__['upper_csm']], inverse=True)
[docs] def fractions(self, data): if len(data) == 0: return None total = np.sum([self.eval(dd) for dd in data]) if total > 0.0: return [self.eval(dd) / total for dd in data] else: return None
[docs] def mean_estimator(self, data): if len(data) == 0: return None elif len(data) == 1: return data[0] else: fractions = self.fractions(data) if fractions is None: return None return np.sum(np.array(fractions) * np.array(data))
ratios = fractions
[docs]class CSMInfiniteRatioFunction(AbstractRatioFunction): ALLOWED_FUNCTIONS = {'power2_inverse_decreasing': ['max_csm'], 'power2_inverse_power2_decreasing': ['max_csm']}
[docs] def power2_inverse_decreasing(self, vals): return power2_inverse_decreasing(vals, edges=[0.0, self.__dict__['max_csm']])
[docs] def power2_inverse_power2_decreasing(self, vals): return power2_inverse_power2_decreasing(vals, edges=[0.0, self.__dict__['max_csm']])
[docs] def fractions(self, data): if len(data) == 0: return None close_to_zero = np.isclose(data, 0.0, atol=1e-10).tolist() nzeros = close_to_zero.count(True) if nzeros == 1: fractions = [0.0] * len(data) fractions[close_to_zero.index(True)] = 1.0 return fractions elif nzeros > 1: raise RuntimeError('Should not have more than one continuous symmetry measure with value equal to 0.0') else: fractions = self.eval(np.array(data)) total = np.sum(fractions) if total > 0.0: return fractions / total else: return None
[docs] def mean_estimator(self, data): if len(data) == 0: return None elif len(data) == 1: return data[0] else: fractions = self.fractions(data) if fractions is None: return None return np.sum(np.array(fractions) * np.array(data))
ratios = fractions
[docs]class DeltaCSMRatioFunction(AbstractRatioFunction): ALLOWED_FUNCTIONS = {'smootherstep': ['delta_csm_min', 'delta_csm_max']}
[docs] def smootherstep(self, vals): return smootherstep(vals, edges=[self.__dict__['delta_csm_min'], self.__dict__['delta_csm_max']])