1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

""" 

Module to read / write Fortran unformatted sequential files. 

 

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

 

""" 

from __future__ import division, print_function, absolute_import 

 

import warnings 

import numpy as np 

 

__all__ = ['FortranFile'] 

 

 

class FortranFile(object): 

""" 

A file object for unformatted sequential files from Fortran code. 

 

Parameters 

---------- 

filename : file or str 

Open file object or filename. 

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

Read-write mode, default is 'r'. 

header_dtype : dtype, optional 

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

 

Notes 

----- 

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

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

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

compilers supporting the BACKSPACE statement will write a second copy of 

the size to facilitate backwards seeking. 

 

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

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

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

 

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

 

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

 

WRITE(1) myvariable 

 

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

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

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

 

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

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

 

Examples 

-------- 

To create an unformatted sequential Fortran file: 

 

>>> from scipy.io import FortranFile 

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

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

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

>>> f.close() 

 

To read this file: 

 

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

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

[1 2 3 4 5] 

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

[[0. 0.05263158 0.10526316 0.15789474] 

[0.21052632 0.26315789 0.31578947 0.36842105] 

[0.42105263 0.47368421 0.52631579 0.57894737] 

[0.63157895 0.68421053 0.73684211 0.78947368] 

[0.84210526 0.89473684 0.94736842 1. ]] 

>>> f.close() 

 

Or, in Fortran:: 

 

integer :: a(5), i 

double precision :: b(5,4) 

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

read(1) a 

read(1) b 

close(1) 

write(*,*) a 

do i = 1, 5 

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

end do 

 

""" 

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

if header_dtype is None: 

raise ValueError('Must specify dtype') 

 

header_dtype = np.dtype(header_dtype) 

if header_dtype.kind != 'u': 

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

 

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

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

 

if hasattr(filename, 'seek'): 

self._fp = filename 

else: 

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

 

self._header_dtype = header_dtype 

 

def _read_size(self): 

return int(np.fromfile(self._fp, dtype=self._header_dtype, count=1)) 

 

def write_record(self, *items): 

""" 

Write a record (including sizes) to the file. 

 

Parameters 

---------- 

*items : array_like 

The data arrays to write. 

 

Notes 

----- 

Writes data items to a file:: 

 

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

 

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

 

Note that data in multidimensional arrays is written in 

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

programs, you need to transpose the arrays yourself when 

writing them. 

 

""" 

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

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

 

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

 

nb.tofile(self._fp) 

for item in items: 

item.tofile(self._fp) 

nb.tofile(self._fp) 

 

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

""" 

Reads a record of a given type from the file. 

 

Parameters 

---------- 

*dtypes : dtypes, optional 

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

 

Returns 

------- 

data : ndarray 

A one-dimensional array object. 

 

Notes 

----- 

If the record contains a multi-dimensional array, you can specify 

the size in the dtype. For example:: 

 

INTEGER var(5,4) 

 

can be read with:: 

 

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

 

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

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

when reading and (ii) transpose the resulting array. 

 

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

ordering yourself. For example:: 

 

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

 

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

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

 

double precision :: a 

integer :: b 

write(1) a, b 

 

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

a = record[0] # first number 

b = record[1] # second number 

 

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

the third item in the relevant dtype:: 

 

double precision :: a 

integer :: b(3,4) 

write(1) a, b 

 

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

a = record[0] 

b = record[1].T 

 

Numpy also supports a short syntax for this kind of type:: 

 

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

 

See Also 

-------- 

read_reals 

read_ints 

 

""" 

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

if kwargs: 

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

 

if dtype is not None: 

dtypes = dtypes + (dtype,) 

elif not dtypes: 

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

 

first_size = self._read_size() 

 

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

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

 

num_blocks, remainder = divmod(first_size, block_size) 

if remainder != 0: 

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

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

 

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

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

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

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

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

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

 

data = [] 

for dtype in dtypes: 

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

if dtype.shape != (): 

# Squeeze outmost block dimension for array items 

if num_blocks == 1: 

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

r = r[0] 

 

data.append(r) 

 

second_size = self._read_size() 

if first_size != second_size: 

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

'this record - check header dtype') 

 

# Unpack result 

if len(dtypes) == 1: 

return data[0] 

else: 

return tuple(data) 

 

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

""" 

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

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

 

Parameters 

---------- 

dtype : dtype, optional 

Data type specifying the size and endiness of the data. 

 

Returns 

------- 

data : ndarray 

A one-dimensional array object. 

 

See Also 

-------- 

read_reals 

read_record 

 

""" 

return self.read_record(dtype) 

 

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

""" 

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

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

 

Parameters 

---------- 

dtype : dtype, optional 

Data type specifying the size and endiness of the data. 

 

Returns 

------- 

data : ndarray 

A one-dimensional array object. 

 

See Also 

-------- 

read_ints 

read_record 

 

""" 

return self.read_record(dtype) 

 

def close(self): 

""" 

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

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

statement in modern versions of Python, to call this automatically 

 

""" 

self._fp.close() 

 

def __enter__(self): 

return self 

 

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

self.close()