Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/io/_fortran.py: 22%

78 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 06:43 +0000

1""" 

2Module to read / write Fortran unformatted sequential files. 

3 

4This is in the spirit of code written by Neil Martinsen-Burrell and Joe Zuntz. 

5 

6""" 

7import warnings 

8import numpy as np 

9 

10__all__ = ['FortranFile', 'FortranEOFError', 'FortranFormattingError'] 

11 

12 

13class FortranEOFError(TypeError, OSError): 

14 """Indicates that the file ended properly. 

15 

16 This error descends from TypeError because the code used to raise 

17 TypeError (and this was the only way to know that the file had 

18 ended) so users might have ``except TypeError:``. 

19 

20 """ 

21 pass 

22 

23 

24class FortranFormattingError(TypeError, OSError): 

25 """Indicates that the file ended mid-record. 

26 

27 Descends from TypeError for backward compatibility. 

28 

29 """ 

30 pass 

31 

32 

33class FortranFile: 

34 """ 

35 A file object for unformatted sequential files from Fortran code. 

36 

37 Parameters 

38 ---------- 

39 filename : file or str 

40 Open file object or filename. 

41 mode : {'r', 'w'}, optional 

42 Read-write mode, default is 'r'. 

43 header_dtype : dtype, optional 

44 Data type of the header. Size and endiness must match the input/output file. 

45 

46 Notes 

47 ----- 

48 These files are broken up into records of unspecified types. The size of 

49 each record is given at the start (although the size of this header is not 

50 standard) and the data is written onto disk without any formatting. Fortran 

51 compilers supporting the BACKSPACE statement will write a second copy of 

52 the size to facilitate backwards seeking. 

53 

54 This class only supports files written with both sizes for the record. 

55 It also does not support the subrecords used in Intel and gfortran compilers 

56 for records which are greater than 2GB with a 4-byte header. 

57 

58 An example of an unformatted sequential file in Fortran would be written as:: 

59 

60 OPEN(1, FILE=myfilename, FORM='unformatted') 

61 

62 WRITE(1) myvariable 

63 

64 Since this is a non-standard file format, whose contents depend on the 

65 compiler and the endianness of the machine, caution is advised. Files from 

66 gfortran 4.8.0 and gfortran 4.1.2 on x86_64 are known to work. 

67 

68 Consider using Fortran direct-access files or files from the newer Stream 

69 I/O, which can be easily read by `numpy.fromfile`. 

70 

71 Examples 

72 -------- 

73 To create an unformatted sequential Fortran file: 

74 

75 >>> from scipy.io import FortranFile 

76 >>> import numpy as np 

77 >>> f = FortranFile('test.unf', 'w') 

78 >>> f.write_record(np.array([1,2,3,4,5], dtype=np.int32)) 

79 >>> f.write_record(np.linspace(0,1,20).reshape((5,4)).T) 

80 >>> f.close() 

81 

82 To read this file: 

83 

84 >>> f = FortranFile('test.unf', 'r') 

85 >>> print(f.read_ints(np.int32)) 

86 [1 2 3 4 5] 

87 >>> print(f.read_reals(float).reshape((5,4), order="F")) 

88 [[0. 0.05263158 0.10526316 0.15789474] 

89 [0.21052632 0.26315789 0.31578947 0.36842105] 

90 [0.42105263 0.47368421 0.52631579 0.57894737] 

91 [0.63157895 0.68421053 0.73684211 0.78947368] 

92 [0.84210526 0.89473684 0.94736842 1. ]] 

93 >>> f.close() 

94 

95 Or, in Fortran:: 

96 

97 integer :: a(5), i 

98 double precision :: b(5,4) 

99 open(1, file='test.unf', form='unformatted') 

100 read(1) a 

101 read(1) b 

102 close(1) 

103 write(*,*) a 

104 do i = 1, 5 

105 write(*,*) b(i,:) 

106 end do 

107 

108 """ 

109 def __init__(self, filename, mode='r', header_dtype=np.uint32): 

110 if header_dtype is None: 

111 raise ValueError('Must specify dtype') 

112 

113 header_dtype = np.dtype(header_dtype) 

114 if header_dtype.kind != 'u': 

115 warnings.warn("Given a dtype which is not unsigned.") 

116 

117 if mode not in 'rw' or len(mode) != 1: 

118 raise ValueError('mode must be either r or w') 

119 

120 if hasattr(filename, 'seek'): 

121 self._fp = filename 

122 else: 

123 self._fp = open(filename, '%sb' % mode) 

124 

125 self._header_dtype = header_dtype 

126 

127 def _read_size(self, eof_ok=False): 

128 n = self._header_dtype.itemsize 

129 b = self._fp.read(n) 

130 if (not b) and eof_ok: 

131 raise FortranEOFError("End of file occurred at end of record") 

132 elif len(b) < n: 

133 raise FortranFormattingError( 

134 "End of file in the middle of the record size") 

135 return int(np.frombuffer(b, dtype=self._header_dtype, count=1)[0]) 

136 

137 def write_record(self, *items): 

138 """ 

139 Write a record (including sizes) to the file. 

140 

141 Parameters 

142 ---------- 

143 *items : array_like 

144 The data arrays to write. 

145 

146 Notes 

147 ----- 

148 Writes data items to a file:: 

149 

150 write_record(a.T, b.T, c.T, ...) 

151 

152 write(1) a, b, c, ... 

153 

154 Note that data in multidimensional arrays is written in 

155 row-major order --- to make them read correctly by Fortran 

156 programs, you need to transpose the arrays yourself when 

157 writing them. 

158 

159 """ 

160 items = tuple(np.asarray(item) for item in items) 

161 total_size = sum(item.nbytes for item in items) 

162 

163 nb = np.array([total_size], dtype=self._header_dtype) 

164 

165 nb.tofile(self._fp) 

166 for item in items: 

167 item.tofile(self._fp) 

168 nb.tofile(self._fp) 

169 

170 def read_record(self, *dtypes, **kwargs): 

171 """ 

172 Reads a record of a given type from the file. 

173 

174 Parameters 

175 ---------- 

176 *dtypes : dtypes, optional 

177 Data type(s) specifying the size and endiness of the data. 

178 

179 Returns 

180 ------- 

181 data : ndarray 

182 A 1-D array object. 

183 

184 Raises 

185 ------ 

186 FortranEOFError 

187 To signal that no further records are available 

188 FortranFormattingError 

189 To signal that the end of the file was encountered 

190 part-way through a record 

191 

192 Notes 

193 ----- 

194 If the record contains a multidimensional array, you can specify 

195 the size in the dtype. For example:: 

196 

197 INTEGER var(5,4) 

198 

199 can be read with:: 

200 

201 read_record('(4,5)i4').T 

202 

203 Note that this function does **not** assume the file data is in Fortran 

204 column major order, so you need to (i) swap the order of dimensions 

205 when reading and (ii) transpose the resulting array. 

206 

207 Alternatively, you can read the data as a 1-D array and handle the 

208 ordering yourself. For example:: 

209 

210 read_record('i4').reshape(5, 4, order='F') 

211 

212 For records that contain several variables or mixed types (as opposed 

213 to single scalar or array types), give them as separate arguments:: 

214 

215 double precision :: a 

216 integer :: b 

217 write(1) a, b 

218 

219 record = f.read_record('<f4', '<i4') 

220 a = record[0] # first number 

221 b = record[1] # second number 

222 

223 and if any of the variables are arrays, the shape can be specified as 

224 the third item in the relevant dtype:: 

225 

226 double precision :: a 

227 integer :: b(3,4) 

228 write(1) a, b 

229 

230 record = f.read_record('<f4', np.dtype(('<i4', (4, 3)))) 

231 a = record[0] 

232 b = record[1].T 

233 

234 NumPy also supports a short syntax for this kind of type:: 

235 

236 record = f.read_record('<f4', '(3,3)<i4') 

237 

238 See Also 

239 -------- 

240 read_reals 

241 read_ints 

242 

243 """ 

244 dtype = kwargs.pop('dtype', None) 

245 if kwargs: 

246 raise ValueError(f"Unknown keyword arguments {tuple(kwargs.keys())}") 

247 

248 if dtype is not None: 

249 dtypes = dtypes + (dtype,) 

250 elif not dtypes: 

251 raise ValueError('Must specify at least one dtype') 

252 

253 first_size = self._read_size(eof_ok=True) 

254 

255 dtypes = tuple(np.dtype(dtype) for dtype in dtypes) 

256 block_size = sum(dtype.itemsize for dtype in dtypes) 

257 

258 num_blocks, remainder = divmod(first_size, block_size) 

259 if remainder != 0: 

260 raise ValueError('Size obtained ({}) is not a multiple of the ' 

261 'dtypes given ({}).'.format(first_size, block_size)) 

262 

263 if len(dtypes) != 1 and first_size != block_size: 

264 # Fortran does not write mixed type array items in interleaved order, 

265 # and it's not possible to guess the sizes of the arrays that were written. 

266 # The user must specify the exact sizes of each of the arrays. 

267 raise ValueError('Size obtained ({}) does not match with the expected ' 

268 'size ({}) of multi-item record'.format(first_size, block_size)) 

269 

270 data = [] 

271 for dtype in dtypes: 

272 r = np.fromfile(self._fp, dtype=dtype, count=num_blocks) 

273 if len(r) != num_blocks: 

274 raise FortranFormattingError( 

275 "End of file in the middle of a record") 

276 if dtype.shape != (): 

277 # Squeeze outmost block dimension for array items 

278 if num_blocks == 1: 

279 assert r.shape == (1,) + dtype.shape 

280 r = r[0] 

281 

282 data.append(r) 

283 

284 second_size = self._read_size() 

285 if first_size != second_size: 

286 raise ValueError('Sizes do not agree in the header and footer for ' 

287 'this record - check header dtype') 

288 

289 # Unpack result 

290 if len(dtypes) == 1: 

291 return data[0] 

292 else: 

293 return tuple(data) 

294 

295 def read_ints(self, dtype='i4'): 

296 """ 

297 Reads a record of a given type from the file, defaulting to an integer 

298 type (``INTEGER*4`` in Fortran). 

299 

300 Parameters 

301 ---------- 

302 dtype : dtype, optional 

303 Data type specifying the size and endiness of the data. 

304 

305 Returns 

306 ------- 

307 data : ndarray 

308 A 1-D array object. 

309 

310 See Also 

311 -------- 

312 read_reals 

313 read_record 

314 

315 """ 

316 return self.read_record(dtype) 

317 

318 def read_reals(self, dtype='f8'): 

319 """ 

320 Reads a record of a given type from the file, defaulting to a floating 

321 point number (``real*8`` in Fortran). 

322 

323 Parameters 

324 ---------- 

325 dtype : dtype, optional 

326 Data type specifying the size and endiness of the data. 

327 

328 Returns 

329 ------- 

330 data : ndarray 

331 A 1-D array object. 

332 

333 See Also 

334 -------- 

335 read_ints 

336 read_record 

337 

338 """ 

339 return self.read_record(dtype) 

340 

341 def close(self): 

342 """ 

343 Closes the file. It is unsupported to call any other methods off this 

344 object after closing it. Note that this class supports the 'with' 

345 statement in modern versions of Python, to call this automatically 

346 

347 """ 

348 self._fp.close() 

349 

350 def __enter__(self): 

351 return self 

352 

353 def __exit__(self, type, value, tb): 

354 self.close()