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
« prev ^ index » next coverage.py v7.10.1, created at 2025-09-14 04:08 +0000
1from collections import OrderedDict
3import numpy as np
5from ase import Atoms
6from icet import ClusterSpace
7from icet.core.sublattices import Sublattices
8from mchammer.calculators.base_calculator import BaseCalculator
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
20 .. math::
21 Q = - \omega L + \sum_{\alpha}
22 \left||\Gamma_{\alpha} - \Gamma^{\text{target}}_{\alpha}\right||.
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`.
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 """
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)
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
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)
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
84 self._cluster_space = cluster_space
85 self._structure = structure
86 self._sublattices = self._cluster_space.get_sublattices(structure)
88 def calculate_total(self, occupations: list[int]) -> float:
89 """
90 Calculates and returns the similarity value :math:`Q`
91 of the current configuration.
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)
106 def calculate_change(self):
107 raise NotImplementedError
109 @property
110 def sublattices(self) -> Sublattices:
111 """ Sublattices of the calculators structure. """
112 return self._sublattices
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.
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