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

52 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-09-14 04:08 +0000

1from collections import OrderedDict 

2 

3import numpy as np 

4 

5from ase import Atoms 

6from icet import ClusterSpace 

7from icet.core.sublattices import Sublattices 

8from mchammer.calculators.base_calculator import BaseCalculator 

9 

10 

11class TargetVectorCalculator(BaseCalculator): 

12 r""" 

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

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

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

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

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

18 :math:`Q` is calculated as 

19 

20 .. math:: 

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

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

23 

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

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

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

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

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

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

30 

31 Parameters 

32 ---------- 

33 structure 

34 Structure for which to set up calculator. 

35 cluster_space 

36 Cluster space from which to build calculator. 

37 target_vector 

38 Vector to which any vector will be compared. 

39 weights 

40 Weighting of each component in cluster vector 

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

42 optimality_weight 

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

44 favors a complete series of optimal cluster correlations 

45 for the smallest pairs (see above). 

46 optimality_tol 

47 Tolerance for determining whether a perfect match 

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

49 name 

50 Human-readable identifier for this calculator. 

51 """ 

52 

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

54 target_vector: list[float], 

55 weights: list[float] = None, 

56 optimality_weight: float = 1.0, 

57 optimality_tol: float = 1e-5, 

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

59 super().__init__(name=name) 

60 

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

62 raise ValueError('Cluster space and target vector ' 

63 'must have the same length') 

64 self.cluster_space = cluster_space 

65 self.target_vector = target_vector 

66 

67 if weights is None: 

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

69 else: 

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

71 raise ValueError('Cluster space and weights ' 

72 'must have the same length') 

73 self.weights = np.array(weights) 

74 

75 if optimality_weight is not None: 

76 self.optimality_weight = optimality_weight 

77 self.optimality_tol = optimality_tol 

78 self.as_list = self.cluster_space.as_list 

79 else: 

80 self.optimality_weight = None 

81 self.optimality_tol = None 

82 self.as_list = None 

83 

84 self._cluster_space = cluster_space 

85 self._structure = structure 

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

87 

88 def calculate_total(self, occupations: list[int]) -> float: 

89 """ 

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

91 of the current configuration. 

92 

93 Parameters 

94 ---------- 

95 occupations 

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

97 """ 

98 self._structure.set_atomic_numbers(occupations) 

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

100 return compare_cluster_vectors(cv, self.target_vector, 

101 self.as_list, 

102 weights=self.weights, 

103 optimality_weight=self.optimality_weight, 

104 tol=self.optimality_tol) 

105 

106 def calculate_change(self): 

107 raise NotImplementedError 

108 

109 @property 

110 def sublattices(self) -> Sublattices: 

111 """ Sublattices of the calculators structure. """ 

112 return self._sublattices 

113 

114 

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

116 as_list: list[OrderedDict], 

117 weights: list[float] = None, 

118 optimality_weight: float = 1.0, 

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

120 """ 

121 Calculate a quantity that measures similarity between two cluster 

122 vecors. 

123 

124 Parameters 

125 ---------- 

126 cv_1 

127 Cluster vector 1. 

128 cv_2 

129 Cluster vector 2. 

130 as_list 

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

132 weights 

133 Weight assigned to each cluster vector element. 

134 optimality_weight 

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

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

137 tol 

138 Numerical tolerance for determining whether two elements are equal. 

139 """ 

140 if weights is None: 

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

142 diff = abs(cv_1 - cv_2) 

143 score = np.dot(diff, weights) 

144 if optimality_weight: 

145 longest_optimal_radius = 0 

146 for orbit_index, d in enumerate(diff): 

147 orbit = as_list[orbit_index] 

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

149 continue 

150 if d < tol: 

151 longest_optimal_radius = orbit['radius'] 

152 else: 

153 break 

154 score -= optimality_weight * longest_optimal_radius 

155 return score