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

1from typing import Dict, List 

2 

3import pandas as pd 

4 

5from _icet import ClusterCounts as _ClusterCounts 

6from ase import Atoms 

7from icet.core.cluster import Cluster 

8from icet.core.local_orbit_list_generator import LocalOrbitListGenerator 

9from icet.core.orbit import Orbit 

10from icet.core.structure import Structure 

11from mchammer.observers.base_observer import BaseObserver 

12import copy 

13 

14 

15class ClusterCountObserver(BaseObserver): 

16 """ 

17 This class represents a cluster count observer. 

18 

19 A cluster count observer enables one to keep track of the 

20 occupation of clusters along the trajectory sampled by a Monte 

21 Carlo (MC) simulation. For example, using this observer, several 

22 canonical MC simulations could be carried out at different 

23 temperatures and the temperature dependence of the number of 

24 nearest neigbhors of a particular species could be accessed with 

25 this observer. 

26 

27 Parameters 

28 ---------- 

29 cluster_space : icet.ClusterSpace 

30 cluster space to define the clusters to be counted 

31 structure : ase.Atoms 

32 defines the lattice that the observer will work on 

33 interval : int 

34 observation interval during the Monte Carlo simulation 

35 max_orbit : int 

36 only include orbits up to the orbit with this index 

37 (default is to include all orbits) 

38 

39 Attributes 

40 ---------- 

41 tag : str 

42 human readable observer name 

43 interval : int 

44 the observation interval, defaults to None meaning that if the 

45 observer is used in a Monte Carlo simulation, then the Ensemble object 

46 will set the interval. 

47 """ 

48 

49 def __init__(self, cluster_space, structure: Atoms, 

50 interval: int = None, 

51 max_orbit: int = None) -> None: 

52 super().__init__(interval=interval, return_type=dict, tag='ClusterCountObserver') 

53 

54 self._cluster_space = cluster_space 

55 local_orbit_list_generator = LocalOrbitListGenerator( 

56 orbit_list=cluster_space.orbit_list, 

57 structure=Structure.from_atoms(structure), 

58 fractional_position_tolerance=cluster_space.fractional_position_tolerance) 

59 

60 self._full_orbit_list = local_orbit_list_generator.generate_full_orbit_list() 

61 self._cluster_counts_cpp = _ClusterCounts() 

62 

63 if max_orbit is None: 

64 self._max_orbit = len(self._full_orbit_list) 

65 else: 

66 self._max_orbit = max_orbit 

67 

68 self._cluster_keys = [] # type: List[Orbit] 

69 for i, orbit in enumerate(self._full_orbit_list.orbits): 

70 cluster = orbit.representative_cluster 

71 cluster.tag = i 

72 self._cluster_keys.append(cluster) 

73 

74 self._empty_counts = self._get_empty_counts() 

75 

76 def _get_empty_counts(self) -> Dict[Cluster, Dict[List[str], int]]: 

77 """ Returns the object which will be filled with counts. """ 

78 counts = {} 

79 for i, cluster in enumerate(self._cluster_keys): 

80 order = len(cluster) 

81 possible_occupations = self._cluster_space.get_possible_orbit_occupations(cluster.tag) 

82 assert order == len(possible_occupations[0]), '{} is not {}, {}'.format( 

83 order, len(possible_occupations[0]), possible_occupations) 

84 

85 counts[cluster] = {occupation: 0 for occupation in possible_occupations} 

86 return counts 

87 

88 def _generate_counts(self, structure: Atoms) -> None: 

89 """Counts the occurrence of different clusters and stores this 

90 information in a pandas dataframe. 

91 

92 Parameters 

93 ---------- 

94 structure 

95 input atomic structure. 

96 """ 

97 self._cluster_counts_cpp.count_orbit_list(Structure.from_atoms(structure), 

98 self._full_orbit_list, True, True, 

99 self._max_orbit) 

100 

101 # Getting the empty counts sometimes constitutes a large part of the total time. 

102 # Thus copy a previously constructed dictionary. 

103 # Since Cluster is not picklable, we need to do a slightly awkward manual copy. 

104 empty_counts = {cluster: copy.deepcopy(item) 

105 for cluster, item in self._empty_counts.items()} 

106 pandas_rows = [] 

107 

108 # std::unordered_map<Cluster, std::map<std::vector<int>, int>> 

109 cluster_counts = self._cluster_counts_cpp.get_cluster_counts() 

110 

111 for cluster_key, chemical_number_counts_dict in cluster_counts.items(): 

112 

113 for chemical_symbols in empty_counts[cluster_key].keys(): 

114 

115 count = chemical_number_counts_dict.get(chemical_symbols, 0) 

116 pandas_row = {} 

117 pandas_row['dc_tag'] = '{}_{}'.format(cluster_key.tag, '_'.join(chemical_symbols)) 

118 pandas_row['occupation'] = chemical_symbols 

119 pandas_row['cluster_count'] = count 

120 pandas_row['orbit_index'] = cluster_key.tag 

121 pandas_row['order'] = len(cluster_key) 

122 pandas_row['radius'] = cluster_key.radius 

123 pandas_rows.append(pandas_row) 

124 self.count_frame = pd.DataFrame(pandas_rows) 

125 self._cluster_counts_cpp.reset() 

126 

127 def get_observable(self, structure: Atoms) -> dict: 

128 """ 

129 Returns the value of the property from a cluster expansion model 

130 for a given atomic configuration. 

131 

132 Parameters 

133 ---------- 

134 structure 

135 input atomic structure 

136 """ 

137 self._generate_counts(structure) 

138 

139 count_dict = {row['dc_tag']: row['cluster_count'] 

140 for i, row in self.count_frame.iterrows()} 

141 return count_dict