Coverage for mchammer/calculators/target_vector_calculator.py: 97%

53 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-12-26 04:12 +0000

1from collections import OrderedDict 

2from typing import List 

3 

4import numpy as np 

5 

6from ase import Atoms 

7from icet import ClusterSpace 

8from icet.core.sublattices import Sublattices 

9from mchammer.calculators.base_calculator import BaseCalculator 

10 

11 

12class TargetVectorCalculator(BaseCalculator): 

13 r""" 

14 A :class:`TargetVectorCalculator` enables evaluation of the similarity 

15 between a structure and a target cluster vector. Such a comparison 

16 can be carried out in many ways, and this implementation follows the 

17 measure proposed by van de Walle *et al.* in Calphad **42**, 13 

18 (2013) [WalTiwJon13]_. Specifically, the objective function 

19 :math:`Q` is calculated as 

20 

21 .. math:: 

22 Q = - \omega L + \sum_{\alpha} 

23 \left||\Gamma_{\alpha} - \Gamma^{\text{target}}_{\alpha}\right||. 

24 

25 Here, :math:`\Gamma_{\alpha}` are components in the cluster vector 

26 and :math:`\Gamma^\text{target}_{\alpha}` the corresponding 

27 target values. The factor :math:`\omega` is the radius of the 

28 largest pair cluster such that all clusters with the same or smaller 

29 radii have :math:`\Gamma_{\alpha} - 

30 \Gamma^\text{target}_{\alpha} = 0`. 

31 

32 Parameters 

33 ---------- 

34 structure 

35 Structure for which to set up calculator. 

36 cluster_space 

37 Cluster space from which to build calculator. 

38 target_vector 

39 Vector to which any vector will be compared. 

40 weights 

41 Weighting of each component in cluster vector 

42 comparison. By default set to 1.0 for all components. 

43 optimality_weight 

44 Factor :math:`L`, a high value of which effectively 

45 favors a complete series of optimal cluster correlations 

46 for the smallest pairs (see above). 

47 optimality_tol 

48 Tolerance for determining whether a perfect match 

49 has been achieved (used in conjunction with :math:`L`). 

50 name 

51 Human-readable identifier for this calculator. 

52 """ 

53 

54 def __init__(self, structure: Atoms, cluster_space: ClusterSpace, 

55 target_vector: List[float], 

56 weights: List[float] = None, 

57 optimality_weight: float = 1.0, 

58 optimality_tol: float = 1e-5, 

59 name: str = 'Target vector calculator') -> None: 

60 super().__init__(name=name) 

61 

62 if len(target_vector) != len(cluster_space): 62 ↛ 63line 62 didn't jump to line 63, because the condition on line 62 was never true

63 raise ValueError('Cluster space and target vector ' 

64 'must have the same length') 

65 self.cluster_space = cluster_space 

66 self.target_vector = target_vector 

67 

68 if weights is None: 

69 weights = np.array([1.0] * len(cluster_space)) 

70 else: 

71 if len(weights) != len(cluster_space): 

72 raise ValueError('Cluster space and weights ' 

73 'must have the same length') 

74 self.weights = np.array(weights) 

75 

76 if optimality_weight is not None: 

77 self.optimality_weight = optimality_weight 

78 self.optimality_tol = optimality_tol 

79 self.as_list = self.cluster_space.as_list 

80 else: 

81 self.optimality_weight = None 

82 self.optimality_tol = None 

83 self.as_list = None 

84 

85 self._cluster_space = cluster_space 

86 self._structure = structure 

87 self._sublattices = self._cluster_space.get_sublattices(structure) 

88 

89 def calculate_total(self, occupations: List[int]) -> float: 

90 """ 

91 Calculates and returns the similarity value :math:`Q` 

92 of the current configuration. 

93 

94 Parameters 

95 ---------- 

96 occupations 

97 The entire occupation vector (i.e., list of atomic species). 

98 """ 

99 self._structure.set_atomic_numbers(occupations) 

100 cv = self.cluster_space.get_cluster_vector(self._structure) 

101 return compare_cluster_vectors(cv, self.target_vector, 

102 self.as_list, 

103 weights=self.weights, 

104 optimality_weight=self.optimality_weight, 

105 tol=self.optimality_tol) 

106 

107 def calculate_change(self): 

108 raise NotImplementedError 

109 

110 @property 

111 def sublattices(self) -> Sublattices: 

112 """ Sublattices of the calculators structure. """ 

113 return self._sublattices 

114 

115 

116def compare_cluster_vectors(cv_1: np.ndarray, cv_2: np.ndarray, 

117 as_list: List[OrderedDict], 

118 weights: List[float] = None, 

119 optimality_weight: float = 1.0, 

120 tol: float = 1e-5) -> float: 

121 """ 

122 Calculate a quantity that measures similarity between two cluster 

123 vecors. 

124 

125 Parameters 

126 ---------- 

127 cv_1 

128 Cluster vector 1. 

129 cv_2 

130 Cluster vector 2. 

131 as_list 

132 Orbit data as obtained by :attr:`ClusterSpace.as_list`. 

133 weights 

134 Weight assigned to each cluster vector element. 

135 optimality_weight 

136 Wuantity :math:`L` in [WalTiwJon13]_ 

137 (see :class:`mchammer.calculators.TargetVectorCalculator`). 

138 tol 

139 Numerical tolerance for determining whether two elements are equal. 

140 """ 

141 if weights is None: 

142 weights = np.ones(len(cv_1)) 

143 diff = abs(cv_1 - cv_2) 

144 score = np.dot(diff, weights) 

145 if optimality_weight: 

146 longest_optimal_radius = 0 

147 for orbit_index, d in enumerate(diff): 

148 orbit = as_list[orbit_index] 

149 if orbit['order'] != 2: 

150 continue 

151 if d < tol: 

152 longest_optimal_radius = orbit['radius'] 

153 else: 

154 break 

155 score -= optimality_weight * longest_optimal_radius 

156 return score