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

104 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-08-01 17:04 +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 

9import numpy as np 

10import hiphive 

11from .logging_tools import logger 

12 

13with warnings.catch_warnings(): 

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

15 import h5py 

16 

17 

18logger = logger.getChild('io_phonopy') 

19 

20 

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

22 """ Tries to guess the format from the filename. """ 

23 basename = os.path.basename(filename) 

24 

25 if basename == 'FORCE_CONSTANTS': 

26 return 'text' 

27 

28 if '.' in basename: 

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

30 if extension == 'hdf5': 

31 return 'hdf5' 

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

33 

34 

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

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

37 

38 Parameters 

39 ---------- 

40 filename 

41 Input file name. 

42 format 

43 Specify the file-format, if `None` tries to guess the format from filename; 

44 possible values: `'text'`, `'hdf5'`. 

45 

46 Returns 

47 ------- 

48 numpy.ndarray 

49 Second-order force constant matrix (`N,N,3,3` array) 

50 where `N` is the number of atoms. 

51 """ 

52 fc2_readers = { 

53 'text': _read_phonopy_fc2_text, 

54 'hdf5': _read_phonopy_fc2_hdf5 

55 } 

56 

57 if format is None: 

58 format = _filename_to_format(filename) 

59 if format not in fc2_readers.keys(): 

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

61 return fc2_readers[format](filename) 

62 

63 

64def write_phonopy_fc2(filename: str, 

65 fc2, 

66 format: str = None) -> None: 

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

68 

69 Parameters 

70 ---------- 

71 filename 

72 Output file name. 

73 fc2 

74 Second-order force constant matrix as :class:`ForceConstants` object or as `np.ndarray`. 

75 format 

76 Specify the file-format, if `None` try to guess the format from filename; 

77 possible values: `'text'`, `'hdf5'`. 

78 """ 

79 

80 fc2_writers = { 

81 'text': _write_phonopy_fc2_text, 

82 'hdf5': _write_phonopy_fc2_hdf5 

83 } 

84 

85 # get fc2_array 

86 if isinstance(fc2, hiphive.ForceConstants): 

87 fc2_array = fc2.get_fc_array(order=2) 

88 elif isinstance(fc2, np.ndarray): 

89 fc2_array = fc2 

90 else: 

91 raise TypeError('fc2 should be a ForceConstants object or a NumPy array.') 

92 

93 # check that fc2 has correct shape 

94 n_atoms = fc2_array.shape[0] 

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

96 raise ValueError('fc2 has wrong shape') 

97 

98 # write 

99 if format is None: 

100 format = _filename_to_format(filename) 

101 if format not in fc2_writers.keys(): 

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

103 fc2_writers[format](filename, fc2_array) 

104 

105 

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

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

108 

109 Parameters 

110 ---------- 

111 filename 

112 Input file name. 

113 

114 Returns 

115 ------- 

116 Third-order force constant matrix. 

117 """ 

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

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

120 fc3 = hf['fc3'][:] 

121 else: 

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

123 return fc3 

124 

125 

126def write_phonopy_fc3( 

127 filename: str, 

128 fc3, 

129) -> None: 

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

131 

132 Parameters 

133 ---------- 

134 filename 

135 Output file name. 

136 fc3 

137 Third-order force constant matrix as :class:`ForceConstants` object or as `np.ndarray`. 

138 """ 

139 

140 if isinstance(fc3, hiphive.ForceConstants): 

141 fc3_array = fc3.get_fc_array(order=3) 

142 elif isinstance(fc3, np.ndarray): 

143 fc3_array = fc3 

144 else: 

145 raise TypeError('fc3 should be a ForceConstants object or a NumPy array.') 

146 

147 # check that fc3 has correct shape 

148 n_atoms = fc3_array.shape[0] 

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

150 raise ValueError('fc3 has wrong shape') 

151 

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

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

154 hf.flush() 

155 

156 

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

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

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

160 

161 # read shape of force constants 

162 line = f.readline() 

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

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

165 n_atoms = line_ints[0] 

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

167 assert line_ints[0] == line_ints[1] 

168 n_atoms = line_ints[0] 

169 else: 

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

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

172 

173 # read force constants 

174 lines = f.readlines() 

175 for n, line in enumerate(lines): 

176 flds = line.split() 

177 if len(flds) == 2: 

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

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

180 for x in range(3): 

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

182 for y in range(3): 

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

184 return fc2 

185 

186 

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

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

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

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

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

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

193 fc2 = hf['fc2'][:] 

194 else: 

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

196 return fc2 

197 

198 

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

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

201 n_atoms = fc2.shape[0] 

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

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

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

205 fc2_ij = fc2[(i, j)] 

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

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

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

209 for row in fc2_ij: 

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

211 

212 

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

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

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

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

217 hf.flush()