Coverage for hiphive/input_output/phonopy.py: 92%

104 statements  

« prev     ^ index     » next       coverage.py v7.6.8, created at 2024-11-28 11:20 +0000

1""" 

2This module provides functions for reading and writing data files 

3in phonopy and phono3py formats. 

4""" 

5 

6import os 

7import warnings 

8from itertools import product 

9 

10import hiphive 

11import numpy as np 

12from .logging_tools import logger 

13 

14with warnings.catch_warnings(): 

15 warnings.filterwarnings('ignore', category=FutureWarning) 

16 import h5py 

17 

18 

19logger = logger.getChild('io_phonopy') 

20 

21 

22def _filename_to_format(filename: str) -> str: 

23 """ Tries to guess the format from the filename """ 

24 basename = os.path.basename(filename) 

25 

26 if basename == 'FORCE_CONSTANTS': 

27 return 'text' 

28 

29 if '.' in basename: 

30 extension = basename.split('.')[-1] 

31 if extension == 'hdf5': 

32 return 'hdf5' 

33 raise ValueError('Could not guess file format') 

34 

35 

36def read_phonopy_fc2(filename: str, format: str = None) -> np.ndarray: 

37 """Parses a second-order force constant file in phonopy format. 

38 

39 Parameters 

40 ---------- 

41 filename : str 

42 input file name 

43 format : str 

44 specify the file-format, if None tries to guess the format from filename; 

45 possible values: "text", "hdf5" 

46 

47 Returns 

48 ------- 

49 numpy.ndarray 

50 second-order force constant matrix (`N,N,3,3` array) 

51 where `N` is the number of atoms 

52 """ 

53 fc2_readers = { 

54 'text': _read_phonopy_fc2_text, 

55 'hdf5': _read_phonopy_fc2_hdf5 

56 } 

57 

58 if format is None: 

59 format = _filename_to_format(filename) 

60 if format not in fc2_readers.keys(): 

61 raise ValueError('Did not recognize format {}'.format(format)) 

62 return fc2_readers[format](filename) 

63 

64 

65def write_phonopy_fc2(filename: str, 

66 fc2, 

67 format: str = None) -> None: 

68 """Writes second-order force constant matrix in phonopy format. 

69 

70 Parameters 

71 ---------- 

72 filename : str 

73 output file name 

74 fc2 : ForceConstants or numpy.ndarray 

75 second-order force constant matrix 

76 format : str 

77 specify the file-format, if None try to guess the format from filename; 

78 possible values: "text", "hdf5" 

79 """ 

80 

81 fc2_writers = { 

82 'text': _write_phonopy_fc2_text, 

83 'hdf5': _write_phonopy_fc2_hdf5 

84 } 

85 

86 # get fc2_array 

87 if isinstance(fc2, hiphive.ForceConstants): 

88 fc2_array = fc2.get_fc_array(order=2) 

89 elif isinstance(fc2, np.ndarray): 

90 fc2_array = fc2 

91 else: 

92 raise TypeError('fc2 should be ForceConstants or NumPy array') 

93 

94 # check that fc2 has correct shape 

95 n_atoms = fc2_array.shape[0] 

96 if fc2_array.shape != (n_atoms, n_atoms, 3, 3): 

97 raise ValueError('fc2 has wrong shape') 

98 

99 # write 

100 if format is None: 

101 format = _filename_to_format(filename) 

102 if format not in fc2_writers.keys(): 

103 raise ValueError('Did not recognize format {}'.format(format)) 

104 fc2_writers[format](filename, fc2_array) 

105 

106 

107def read_phonopy_fc3(filename: str) -> np.ndarray: 

108 """Parses a third order force constant file in phonopy hdf5 format. 

109 

110 Parameters 

111 ---------- 

112 filename : str 

113 input file name 

114 

115 Returns 

116 ------- 

117 numpy.ndarray 

118 third order force constant matrix 

119 """ 

120 with h5py.File(filename, 'r') as hf: 

121 if 'fc3' in hf.keys(): 121 ↛ 124line 121 didn't jump to line 124 because the condition on line 121 was always true

122 fc3 = hf['fc3'][:] 

123 else: 

124 raise IOError('Could not find fc3 in file {}'.format(filename)) 

125 return fc3 

126 

127 

128def write_phonopy_fc3(filename: str, fc3) -> None: 

129 """Writes third order force constant matrix in phonopy hdf5 format. 

130 

131 Parameters 

132 ---------- 

133 filename : str 

134 output file name 

135 fc3 : ForceConstants or numpy.ndarray 

136 third order force constant matrix 

137 """ 

138 

139 if isinstance(fc3, hiphive.ForceConstants): 

140 fc3_array = fc3.get_fc_array(order=3) 

141 elif isinstance(fc3, np.ndarray): 

142 fc3_array = fc3 

143 else: 

144 raise TypeError('fc3 should be ForceConstants or NumPy array') 

145 

146 # check that fc3 has correct shape 

147 n_atoms = fc3_array.shape[0] 

148 if fc3_array.shape != (n_atoms, n_atoms, n_atoms, 3, 3, 3): 

149 raise ValueError('fc3 has wrong shape') 

150 

151 with h5py.File(filename, 'w') as hf: 

152 hf.create_dataset('fc3', data=fc3_array, compression='gzip') 

153 hf.flush() 

154 

155 

156def _read_phonopy_fc2_text(filename: str) -> np.ndarray: 

157 """ Reads phonopy-fc2 file in text format. """ 

158 with open(filename, 'r') as f: 

159 

160 # read shape of force constants 

161 line = f.readline() 

162 line_ints = [int(x) for x in line.split()] 

163 if len(line_ints) == 1: 163 ↛ 164line 163 didn't jump to line 164 because the condition on line 163 was never true

164 n_atoms = line_ints[0] 

165 elif len(line_ints) == 2: 165 ↛ 169line 165 didn't jump to line 169 because the condition on line 165 was always true

166 assert line_ints[0] == line_ints[1] 

167 n_atoms = line_ints[0] 

168 else: 

169 raise ValueError('Unsupported or incorrect phonopy format') 

170 fc2 = np.full((n_atoms, n_atoms, 3, 3), np.nan) 

171 

172 # read force constants 

173 lines = f.readlines() 

174 for n, line in enumerate(lines): 

175 flds = line.split() 

176 if len(flds) == 2: 

177 i = int(flds[0]) - 1 # phonopy index starts with 1 

178 j = int(flds[1]) - 1 

179 for x in range(3): 

180 fc_row = lines[n + x + 1].split() 

181 for y in range(3): 

182 fc2[i][j][x][y] = float(fc_row[y]) 

183 return fc2 

184 

185 

186def _read_phonopy_fc2_hdf5(filename: str) -> np.ndarray: 

187 """ Reads phonopy-fc2 file in hdf5 format. """ 

188 with h5py.File(filename, 'r') as hf: 

189 if 'force_constants' in hf.keys(): 189 ↛ 190line 189 didn't jump to line 190 because the condition on line 189 was never true

190 fc2 = hf['force_constants'][:] 

191 elif 'fc2' in hf.keys(): 191 ↛ 194line 191 didn't jump to line 194 because the condition on line 191 was always true

192 fc2 = hf['fc2'][:] 

193 else: 

194 raise IOError('Could not find fc2 in file {}'.format(filename)) 

195 return fc2 

196 

197 

198def _write_phonopy_fc2_text(filename: str, fc2: np.ndarray) -> None: 

199 """ Writes second-order force constants to file in text format. """ 

200 n_atoms = fc2.shape[0] 

201 with open(filename, 'w') as f: 

202 f.write('{:5d} {:5d}\n'.format(n_atoms, n_atoms)) 

203 for i, j in product(range(n_atoms), repeat=2): 

204 fc2_ij = fc2[(i, j)] 

205 if fc2_ij.shape != (3, 3): 205 ↛ 206line 205 didn't jump to line 206 because the condition on line 205 was never true

206 raise ValueError('Invalid shape for fc2[({},{})]'.format(i, j)) 

207 f.write('{:-5d}{:5d}\n'.format(i + 1, j + 1)) 

208 for row in fc2_ij: 

209 f.write((3*' {:22.15f}'+'\n').format(*tuple(row))) 

210 

211 

212def _write_phonopy_fc2_hdf5(filename: str, fc2: np.ndarray) -> None: 

213 """ Writes second-order force constants to file in hdf5 format. """ 

214 with h5py.File(filename, 'w') as hf: 

215 hf.create_dataset('fc2', data=fc2, compression='gzip') 

216 hf.flush()