Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

from typing import List, Union 

 

import numpy as np 

 

from _icet import _ClusterExpansionCalculator 

from icet.io.logging import logger 

from ase import Atoms 

from icet import ClusterExpansion, Structure 

from icet.core.sublattices import Sublattices 

from mchammer.calculators.base_calculator import BaseCalculator 

 

 

class ClusterExpansionCalculator(BaseCalculator): 

"""A ClusterExpansionCalculator object enables the efficient 

calculation of properties described by a cluster expansion. It is 

specific for a particular (supercell) structure and commonly 

employed when setting up a Monte Carlo simulation, see 

:ref:`ensembles`. 

 

Cluster expansions, e.g., of the energy, typically yield property 

values *per site*. When running a Monte Carlo simulation one, 

however, considers changes in the *total* energy of the 

system. The default behavior is therefore to multiply the output 

of the cluster expansion by the number of sites. This behavior can 

be changed via the ``scaling`` keyword parameter. 

 

Parameters 

---------- 

structure : ase.Atoms 

structure for which to set up the calculator 

cluster_expansion : ClusterExpansion 

cluster expansion from which to build calculator 

name 

human-readable identifier for this calculator 

scaling 

scaling factor applied to the property value predicted by the 

cluster expansion 

use_local_energy_calculator 

evaluate energy changes using only the local environment; this method 

is generally *much* faster; unless you know what you are doing do *not* 

set this option to `False` 

""" 

 

def __init__(self, 

structure: Atoms, cluster_expansion: ClusterExpansion, 

name: str = 'Cluster Expansion Calculator', 

scaling: Union[float, int] = None, 

use_local_energy_calculator: bool = True) -> None: 

super().__init__(structure=structure, name=name) 

 

structure_cpy = structure.copy() 

cluster_expansion.prune() 

 

if cluster_expansion._cluster_space.is_supercell_self_correlated(structure): 

logger.warning('The ClusterExpansionCalculator self-interacts, ' 

'which may lead to erroneous results. To avoid ' 

'self-interaction, use a larger supercell or a ' 

'cluster space with shorter cutoffs.') 

 

self.use_local_energy_calculator = use_local_energy_calculator 

61 ↛ 66line 61 didn't jump to line 66, because the condition on line 61 was never false if self.use_local_energy_calculator: 

self.cpp_calc = _ClusterExpansionCalculator( 

cluster_expansion.get_cluster_space_copy(), 

Structure.from_atoms(structure_cpy)) 

 

self._cluster_expansion = cluster_expansion 

67 ↛ 70line 67 didn't jump to line 70, because the condition on line 67 was never false if scaling is None: 

self._property_scaling = len(structure) 

else: 

self._property_scaling = scaling 

 

@property 

def cluster_expansion(self) -> ClusterExpansion: 

""" cluster expansion from which calculator was constructed """ 

return self._cluster_expansion 

 

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

""" 

Calculates and returns the total property value of the current 

configuration. 

 

Parameters 

---------- 

occupations 

the entire occupation vector (i.e. list of atomic species) 

""" 

self.structure.set_atomic_numbers(occupations) 

return self.cluster_expansion.predict(self.structure) * \ 

self._property_scaling 

 

def calculate_local_contribution(self, *, local_indices: List[int], 

occupations: List[int]) -> float: 

""" 

Calculates and returns the sum of the contributions to the property 

due to the sites specified in `local_indices` 

 

Parameters 

---------- 

local_indices 

sites over which to sum up the local contribution 

occupations 

entire occupation vector 

""" 

104 ↛ 105line 104 didn't jump to line 105, because the condition on line 104 was never true if not self.use_local_energy_calculator: 

return self.calculate_total(occupations=occupations) 

 

self.structure.set_atomic_numbers(occupations) 

 

local_contribution = 0 

exclude_indices = [] # type: List[int] 

 

for index in local_indices: 

try: 

local_contribution += self._calculate_local_contribution( 

index, exclude_indices=exclude_indices) 

except Exception as e: 

msg = "caugh exception {}. Try setting flag ".format(e) 

msg += "`use_local_energy_calculator to False` in init" 

raise RuntimeError(msg) 

 

exclude_indices.append(index) 

 

return local_contribution * self._property_scaling 

 

def _calculate_local_contribution(self, index: int, exclude_indices: List[int] = []): 

""" 

Internal method to calculate the local contribution for one 

index. 

 

Parameters 

---------- 

index : int 

lattice index 

exclude_indices 

previously calculated indices, these indices will 

be ignored in order to avoid double counting bonds 

 

""" 

local_cv = self.cpp_calc.get_local_cluster_vector( 

self.structure.get_atomic_numbers(), index, exclude_indices) 

return np.dot(local_cv, self.cluster_expansion.parameters) 

 

@property 

def sublattices(self) -> Sublattices: 

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

sl = self.cluster_expansion._cluster_space.get_sublattices(self.structure) 

return sl