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
« 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.
4This is in the spirit of code written by Neil Martinsen-Burrell and Joe Zuntz.
6"""
7import warnings
8import numpy as np
10__all__ = ['FortranFile', 'FortranEOFError', 'FortranFormattingError']
13class FortranEOFError(TypeError, OSError):
14 """Indicates that the file ended properly.
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:``.
20 """
21 pass
24class FortranFormattingError(TypeError, OSError):
25 """Indicates that the file ended mid-record.
27 Descends from TypeError for backward compatibility.
29 """
30 pass
33class FortranFile:
34 """
35 A file object for unformatted sequential files from Fortran code.
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.
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.
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.
58 An example of an unformatted sequential file in Fortran would be written as::
60 OPEN(1, FILE=myfilename, FORM='unformatted')
62 WRITE(1) myvariable
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.
68 Consider using Fortran direct-access files or files from the newer Stream
69 I/O, which can be easily read by `numpy.fromfile`.
71 Examples
72 --------
73 To create an unformatted sequential Fortran file:
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()
82 To read this file:
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()
95 Or, in Fortran::
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
108 """
109 def __init__(self, filename, mode='r', header_dtype=np.uint32):
110 if header_dtype is None:
111 raise ValueError('Must specify dtype')
113 header_dtype = np.dtype(header_dtype)
114 if header_dtype.kind != 'u':
115 warnings.warn("Given a dtype which is not unsigned.")
117 if mode not in 'rw' or len(mode) != 1:
118 raise ValueError('mode must be either r or w')
120 if hasattr(filename, 'seek'):
121 self._fp = filename
122 else:
123 self._fp = open(filename, '%sb' % mode)
125 self._header_dtype = header_dtype
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])
137 def write_record(self, *items):
138 """
139 Write a record (including sizes) to the file.
141 Parameters
142 ----------
143 *items : array_like
144 The data arrays to write.
146 Notes
147 -----
148 Writes data items to a file::
150 write_record(a.T, b.T, c.T, ...)
152 write(1) a, b, c, ...
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.
159 """
160 items = tuple(np.asarray(item) for item in items)
161 total_size = sum(item.nbytes for item in items)
163 nb = np.array([total_size], dtype=self._header_dtype)
165 nb.tofile(self._fp)
166 for item in items:
167 item.tofile(self._fp)
168 nb.tofile(self._fp)
170 def read_record(self, *dtypes, **kwargs):
171 """
172 Reads a record of a given type from the file.
174 Parameters
175 ----------
176 *dtypes : dtypes, optional
177 Data type(s) specifying the size and endiness of the data.
179 Returns
180 -------
181 data : ndarray
182 A 1-D array object.
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
192 Notes
193 -----
194 If the record contains a multidimensional array, you can specify
195 the size in the dtype. For example::
197 INTEGER var(5,4)
199 can be read with::
201 read_record('(4,5)i4').T
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.
207 Alternatively, you can read the data as a 1-D array and handle the
208 ordering yourself. For example::
210 read_record('i4').reshape(5, 4, order='F')
212 For records that contain several variables or mixed types (as opposed
213 to single scalar or array types), give them as separate arguments::
215 double precision :: a
216 integer :: b
217 write(1) a, b
219 record = f.read_record('<f4', '<i4')
220 a = record[0] # first number
221 b = record[1] # second number
223 and if any of the variables are arrays, the shape can be specified as
224 the third item in the relevant dtype::
226 double precision :: a
227 integer :: b(3,4)
228 write(1) a, b
230 record = f.read_record('<f4', np.dtype(('<i4', (4, 3))))
231 a = record[0]
232 b = record[1].T
234 NumPy also supports a short syntax for this kind of type::
236 record = f.read_record('<f4', '(3,3)<i4')
238 See Also
239 --------
240 read_reals
241 read_ints
243 """
244 dtype = kwargs.pop('dtype', None)
245 if kwargs:
246 raise ValueError(f"Unknown keyword arguments {tuple(kwargs.keys())}")
248 if dtype is not None:
249 dtypes = dtypes + (dtype,)
250 elif not dtypes:
251 raise ValueError('Must specify at least one dtype')
253 first_size = self._read_size(eof_ok=True)
255 dtypes = tuple(np.dtype(dtype) for dtype in dtypes)
256 block_size = sum(dtype.itemsize for dtype in dtypes)
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))
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))
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]
282 data.append(r)
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')
289 # Unpack result
290 if len(dtypes) == 1:
291 return data[0]
292 else:
293 return tuple(data)
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).
300 Parameters
301 ----------
302 dtype : dtype, optional
303 Data type specifying the size and endiness of the data.
305 Returns
306 -------
307 data : ndarray
308 A 1-D array object.
310 See Also
311 --------
312 read_reals
313 read_record
315 """
316 return self.read_record(dtype)
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).
323 Parameters
324 ----------
325 dtype : dtype, optional
326 Data type specifying the size and endiness of the data.
328 Returns
329 -------
330 data : ndarray
331 A 1-D array object.
333 See Also
334 --------
335 read_ints
336 read_record
338 """
339 return self.read_record(dtype)
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
347 """
348 self._fp.close()
350 def __enter__(self):
351 return self
353 def __exit__(self, type, value, tb):
354 self.close()