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

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

import pickle 

import numpy as np 

from ase.neighborlist import NeighborList 

from hiphive.input_output.pretty_table_prints import table_array_to_string 

 

 

# TODO: Use another base class to hide internals from user 

class Cutoffs: 

""" This class maintains information about the cutoff configuration, 

i.e. which clusters will be included (="inside cutoff"). It also 

encapsulates functionality that is used e.g., during cluster space 

construction. 

 

Here, `n-body` refers to number of atoms in a cluster. For example 

the cluster (0011) is a two-body cluster of fourth order and the 

cluster (123) is a three-body cluster of third order. 

 

Parameters 

---------- 

cutoff_matrix : numpy.ndarray 

the matrix element `ij` provides to the cutoff for order `j+2` 

and nbody `i+2`; elements with `i>j` will be ignored 

""" 

 

def __init__(self, cutoff_matrix): 

 

self._cutoff_matrix = np.array(cutoff_matrix, dtype=np.float) 

 

29 ↛ 30line 29 didn't jump to line 30, because the condition on line 29 was never true if len(self._cutoff_matrix.shape) != 2: 

raise ValueError('Please specify cutoff matrix as a 2D array') 

for i, row in enumerate(self._cutoff_matrix): 

32 ↛ 33line 32 didn't jump to line 33, because the condition on line 32 was never true if np.any(row[i:] < 0): 

raise ValueError('Negative number as cutoff') 

row[:i] = np.NaN 

self._cutoff_matrix = self._cutoff_matrix[:(self.max_nbody-1), :(self.max_order-1)] 

 

@property 

def cutoff_matrix(self): 

""" numpy.ndarray : copy of cutoff matrix """ 

return self._cutoff_matrix.copy() 

 

@property 

def orders(self): 

""" list(int) : allowed orders """ 

return list(range(2, self.max_order + 1)) 

 

@property 

def nbodies(self): 

""" list(int) : allowed bodies """ 

return list(range(2, self.max_nbody + 1)) 

 

def get_cutoff(self, order, nbody): 

""" 

Returns cutoff for a given body and order. 

 

Parameters 

---------- 

order : int 

nbody : int 

 

Raises 

------ 

ValueError 

if order is not in orders 

ValueError 

if nbody is not in nbodies 

ValueError 

if nbody is larger than order 

 

Returns 

------- 

float 

""" 

74 ↛ 75line 74 didn't jump to line 75, because the condition on line 74 was never true if order not in self.orders: 

raise ValueError('order not in orders') 

76 ↛ 77line 76 didn't jump to line 77, because the condition on line 76 was never true if nbody not in self.nbodies: 

raise ValueError('nbody not in nbodies') 

78 ↛ 79line 78 didn't jump to line 79, because the condition on line 78 was never true if nbody > order: 

raise ValueError('nbody can not be larger than order') 

return self._cutoff_matrix[nbody - 2, order - 2] 

 

@property 

def max_cutoff(self): 

""" float : maximum cutoff """ 

max_cutoff = 0 

for i, row in enumerate(self._cutoff_matrix): 

max_cutoff = max(max_cutoff, np.max(row[i:])) 

return max_cutoff 

 

@property 

def max_nbody(self): 

""" int : maximum body """ 

nbody = 1 

for i, row in enumerate(self._cutoff_matrix): 

95 ↛ 94line 95 didn't jump to line 94, because the condition on line 95 was never false if np.any(row[i:]): 

nbody = i + 2 

return nbody 

 

@property 

def max_order(self): 

""" int : maximum order """ 

order = None 

for col in range(self._cutoff_matrix.shape[1]): 

104 ↛ 103line 104 didn't jump to line 103, because the condition on line 104 was never false if np.any(self._cutoff_matrix[:col + 1, col]): 

order = col + 2 

return order 

 

def max_nbody_cutoff(self, nbody): 

""" Return maximum cutoff for a given body. """ 

110 ↛ 111line 110 didn't jump to line 111, because the condition on line 110 was never true if nbody not in self.nbodies: 

raise ValueError('nbody not in nbodies') 

return np.max(self._cutoff_matrix[nbody - 2, max(0, nbody - 2):]) 

 

def max_nbody_order(self, nbody): 

""" Returns maximum order for a given body """ 

116 ↛ 117line 116 didn't jump to line 117, because the condition on line 116 was never true if nbody not in self.nbodies: 

raise ValueError('nbody not in nbodies') 

row = self._cutoff_matrix[nbody - 2] 

max_order = None 

for order, cutoff in enumerate(row[nbody-2:], start=nbody): 

121 ↛ 120line 121 didn't jump to line 120, because the condition on line 121 was never false if cutoff: 

max_order = order 

return max_order 

 

def write(self, fileobj): 

""" Writes instance to file. 

 

Parameters 

---------- 

fileobj : file-like object 

file-like object to which the cutoffs will be written to 

""" 

pickle.dump(self._cutoff_matrix, fileobj) 

 

def read(fileobj): 

""" Reads an instance from file. 

 

Parameters 

---------- 

fileobj : file-like object 

input file to read from 

""" 

data = pickle.load(fileobj) 

return Cutoffs(data) 

 

def to_filename_tag(self): 

""" Simple function turning cutoffs into a string to be used in e.g. 

filenames. """ 

s = [] 

for i, c in enumerate(self._cutoff_matrix.tolist(), start=2): 

s.append('{}body-{}'.format(i, '_'.join(map(str, c)))) 

return '_'.join(s) 

 

def __str__(self): 

cutoff_matrix = self._cutoff_matrix.copy() 

cutoff_matrix = np.vstack(([[None] * len(self.orders)], cutoff_matrix)) 

s = table_array_to_string(cutoff_matrix) 

 

width = max(len(c) for c in s.split('\n')) 

header = ' Cutoffs '.center(width, '=') + '\n' 

bottom = '\n' + ''.center(width, '=') 

s = header + s + bottom 

return s 

 

def __repr__(self): 

return 'Cutoffs({!r})'.format(self._cutoff_matrix) 

 

 

class CutoffMaximumBody(Cutoffs): 

""" Specify cutoff-list plus maximum body 

 

Usefull when creating e.g. 6th order expansions but with only 3-body 

interactions. 

 

Parameters 

---------- 

cutoff_list : list 

list of cutoffs for order 2, 3, etc. Must be in decresing order 

max_nbody : int 

No clusters containing more than max_nbody atoms will be generated 

""" 

 

def __init__(self, cutoff_list, max_nbody): 

cutoff_matrix = np.zeros((max_nbody - 1, len(cutoff_list))) 

for order, cutoff in enumerate(cutoff_list, start=2): 

cutoff_matrix[:, order - 2] = cutoff 

super().__init__(cutoff_matrix) 

 

 

def is_cutoff_allowed(atoms, cutoff): 

""" Checks if atoms is compatible with cutoff 

 

Parameters 

---------- 

atoms : ase.Atoms 

structure used for checking compatibility with cutoff 

cutoff : float 

cutoff to be tested 

 

Returns 

------- 

bool 

True if cutoff compatible with atoms object, else False 

""" 

nbrlist = NeighborList(cutoffs=[cutoff / 2] * len(atoms), skin=0, 

self_interaction=False, bothways=True) 

nbrlist.update(atoms) 

 

for i in range(len(atoms)): 

neighbors, _ = nbrlist.get_neighbors(i) 

if i in neighbors: 

return False 

if len(neighbors) != len(set(neighbors)): 

return False 

return True 

 

 

def estimate_maximum_cutoff(atoms, max_iter=11): 

""" Estimates the maximum possible cutoff given the atoms object 

 

Parameters 

---------- 

atoms : ase.Atoms 

structure used for checking compatibility with cutoff 

max_iter : int 

number of iterations in binary search 

""" 

 

# First upper boundary of cutoff 

upper_cutoff = min(np.linalg.norm(atoms.cell, axis=1)) 

 

# generate all possible offsets given upper_cutoff 

nbrlist = NeighborList(cutoffs=[upper_cutoff / 2] * len(atoms), skin=0, 

self_interaction=False, bothways=True) 

nbrlist.update(atoms) 

all_offsets = [] 

for i in range(len(atoms)): 

_, offsets = nbrlist.get_neighbors(i) 

all_offsets.extend([tuple(offset) for offset in offsets]) 

 

# find lower boundary and new upper boundary 

unique_offsets = set(all_offsets) 

unique_offsets.discard((0, 0, 0)) 

upper = min(np.linalg.norm(np.dot(offset, atoms.cell)) 

for offset in unique_offsets) 

lower = upper / 2.0 

 

# run binary search between the upper and lower bounds 

for _ in range(max_iter): 

cutoff = (upper + lower) / 2 

if is_cutoff_allowed(atoms, cutoff): 

lower = cutoff 

else: 

upper = cutoff 

return lower 

 

 

class BaseClusterFilter: 

"""Base cluster filter class. 

 

This filter simply accepts all proposed clusters. A proper 

subclass must implement the same methods. 

""" 

def setup(self, atoms): 

""" The filter is passed the environment of the primitive cell. 

 

Parameters 

---------- 

atoms : ase.Atoms 

non-pbc primitive cell plus neighboring atoms 

""" 

self._atoms = atoms 

 

def __call__(self, cluster): 

""" Returns True or False when a cluster is proposed. 

 

Parameters 

---------- 

cluster : tuple(int) 

indices of proposed cluster referenced to the internal 

atoms object 

""" 

return True