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
« 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"""
6import os
7import warnings
8from itertools import product
9import numpy as np
10import hiphive
11from .logging_tools import logger
13with warnings.catch_warnings():
14 warnings.filterwarnings('ignore', category=FutureWarning)
15 import h5py
18logger = logger.getChild('io_phonopy')
21def _filename_to_format(filename: str) -> str:
22 """ Tries to guess the format from the filename. """
23 basename = os.path.basename(filename)
25 if basename == 'FORCE_CONSTANTS':
26 return 'text'
28 if '.' in basename:
29 extension = basename.split('.')[-1]
30 if extension == 'hdf5':
31 return 'hdf5'
32 raise ValueError('Could not guess file format')
35def read_phonopy_fc2(filename: str, format: str = None) -> np.ndarray:
36 """Parses a second-order force constant file in phonopy format.
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'`.
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 }
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)
64def write_phonopy_fc2(filename: str,
65 fc2,
66 format: str = None) -> None:
67 """Writes second-order force constant matrix in phonopy format.
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 """
80 fc2_writers = {
81 'text': _write_phonopy_fc2_text,
82 'hdf5': _write_phonopy_fc2_hdf5
83 }
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.')
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')
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)
106def read_phonopy_fc3(filename: str) -> np.ndarray:
107 """Parses a third order force constant file in phonopy hdf5 format.
109 Parameters
110 ----------
111 filename
112 Input file name.
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
126def write_phonopy_fc3(
127 filename: str,
128 fc3,
129) -> None:
130 """Writes third order force constant matrix in phonopy hdf5 format.
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 """
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.')
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')
152 with h5py.File(filename, 'w') as hf:
153 hf.create_dataset('fc3', data=fc3_array, compression='gzip')
154 hf.flush()
157def _read_phonopy_fc2_text(filename: str) -> np.ndarray:
158 """ Reads phonopy-fc2 file in text format. """
159 with open(filename, 'r') as f:
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)
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
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
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)))
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()