[PYTHON MATRIX-SIG] Fortran I/O

Konrad Hinsen hinsen@ibs.ibs.fr
Fri, 20 Sep 96 17:14:51 +0100


Here's something for those poor fellows who have to deal with Fortran
programs: Fortran-style I/O in Python. It should be considered a beta
release, since no one by myself has tested it. I'd welcome any
comments and suggestions.

Konrad.
---------------------------------------------------------------------------
-- FortranFormat.py -------------------------------------------------------
---------------------------------------------------------------------------
# This module defines a class that handles I/O using
# Fortran-compatible format specifications.
#
#
# Warning: Fortran formatting is a complex business and I don't
# claim that this module works for anything complicated. It knows
# only the most frequent formatting options. Known limitations:
#
# 1) Only A, D, E, F, G, I, and X formats are supported (plus string constants
#    for output).
# 2) No direct support for complex numbers. You have to split them into
#    real and imaginary parts before output, and for input you get
#    two float numbers anyway.
#
#
# Written by Konrad Hinsen <hinsen@ibs.ibs.fr>
# last revision: 1996-9-20
#

"""Fortran-compatible input/output

This module provides two classes that aid in reading and writing
Fortran-formatted text files. Only a subset of formatting options
is supported: A, D, E, F, G, I, and X formats, plus string constants
for output. Repetition (e.g. 4I5 or 3(1X,A4)) is supported. Complex
numbers are not supported; you have to treat real and imaginary parts
separately.

Examples:
=========

Input:
------
>> s = '   59999'
>> format = FortranFormat('2I4')
>> line = FortranLine(s, format)
>> line[0]
5
>> line[1]
9999

Output:
-------
>> format = FortranFormat('2D15.5')
>> line = FortranLine([3.1415926, 2.71828], format)
>> line.text
'    3.14159D+00    2.71828D+00'

The second argumet to FortranLine can be a format object or a
string (that is then converted into a format object). If the
same format is to be used several times, it is more efficient
to convert it into a format object once - parsing the format
string is a relatively expensive operation.
"""

import string

#
# The class FortranLine represents a single line of input/output,
# which can be accessed as text or as a list of items.
#
class FortranLine:

    def __init__(self, line, format, length = 80):
	if type(line) == type(''):
	    self.text = line
	    self.data = None
	else:
	    self.text = None
	    self.data = line
	if type(format) == type(''):
	    self.format = FortranFormat(format)
	else:
	    self.format = format
	self.length = length
	if self.text is None:
	    self._output()
	if self.data is None:
	    self._input()

    def __len__(self):
	return len(self.data)

    def __getitem__(self, i):
	return self.data[i]

    def __getslice__(self, i, j):
	return self.data[i:j]

    def isBlank(self):
	return len(string.strip(self.text)) == 0

    def _input(self):
	text = self.text
	if len(text) < self.length: text = text + (self.length-len(text))*' '
	self.data = []
	for field in self.format:
	    l = field[1]
	    s = text[:l]
	    text = text[l:]
	    type = field[0]
	    value = None
	    if type == 'A':
		value = s
	    elif type == 'I':
		s = string.strip(s)
		if len(s) == 0:
		    value = 0
		else:
		    value = string.atoi(s)
	    elif type == 'D' or type == 'E' or type == 'F' or type == 'G':
		s = string.strip(s)
		if len(s) == 0:
		    value = 0.
		else:
		    value = string.atof(s)
	    if value is not None:
		self.data.append(value)

    def _output(self):
	data = self.data
	self.text = ''
	for field in self.format:
	    type = field[0]
	    if type == "'":
		self.text = self.text + field[1]
	    elif type == 'X':
		self.text = self.text + field[1]*' '
	    else: # fields that use input data
		length = field[1]
		if len(field) > 2: fraction = field[2]
		value = data[0]
		data = data[1:]
		if type == 'A':
		    self.text = self.text + (value+length*' ')[:length]
		else: # numeric fields
		    if type == 'I':
			s = `value`
		    elif type == 'D':
			s = ('%'+`length`+'.'+`fraction`+'e') % value
			n = string.find(s, 'e')
			s = s[:n] + 'D' + s[n+1:]
		    elif type == 'E':
			s = ('%'+`length`+'.'+`fraction`+'e') % value
		    elif type == 'F':
			s = ('%'+`length`+'.'+`fraction`+'f') % value
		    elif type == 'G':
			s = ('%'+`length`+'.'+`fraction`+'g') % value
		    else:
			raise ValueError, 'Not yet implemented'
		    s = string.upper(s)
		    self.text = self.text + ((length*' ')+s)[-length:]

#
# The class FortranFormat represents a format specification.
# It ought to work for correct specifications, but there is
# little error checking.
#
class FortranFormat:

    def __init__(self, format, nested = 0):
	fields = []
	format = string.strip(format)
	while format and format[0] != ')':
	    n = 0
	    while format[0] in string.digits:
		n = 10*n + string.atoi(format[0])
		format = format[1:]
	    if n == 0: n = 1
	    type = string.upper(format[0])
	    if type == "'":
		eof = string.find(format, "'", 1)
		text = format[1:eof]
		format = format[eof+1:]
	    else:
		format = string.strip(format[1:])
	    if type == '(':
		subformat = FortranFormat(format, 1)
		fields = fields + n*subformat.fields
		format = subformat.rest
		eof = string.find(format, ',')
		if eof >= 0:
		    format = format[eof+1:]
	    else:
		eof = string.find(format, ',')
		if eof >= 0:
		    field = format[:eof]
		    format = format[eof+1:]
		else:
		    eof = string.find(format, ')')
		    if eof >= 0:
			field = format[:eof]
			format = format[eof+1:]
		    else:
			field = format
			format = ''
		if type == "'":
		    field = (type, text)
		else:
		    dot = string.find(field, '.')
		    if dot > 0:
			length = string.atoi(field[:dot])
			fraction = string.atoi(field[dot+1:])
			field = (type, length, fraction)
		    else:
			if field:
			    length = string.atoi(field)
			else:
			    length = 1
			field = (type, length)
		fields = fields + n*[field]
	self.fields = fields
	if nested:
	    self.rest = format

    def __len__(self):
	return len(self.fields)

    def __getitem__(self, i):
	return self.fields[i]


if __name__ == '__main__':
    f = FortranFormat("'!!',D10.3,F10.3,G10.3,'!!'")
    l = FortranLine([1.5707963, 3.14159265358, 2.71828], f)
    print l.text
---------------------------------------------------------------------------
-- end of FortranFormat.py ------------------------------------------------
---------------------------------------------------------------------------

-------------------------------------------------------------------------------
Konrad Hinsen                          | E-Mail: hinsen@ibs.ibs.fr
Laboratoire de Dynamique Moleculaire   | Tel.: +33-76.88.99.28
Institut de Biologie Structurale       | Fax:  +33-76.88.54.94
41, av. des Martyrs                    | Deutsch/Esperanto/English/
38027 Grenoble Cedex 1, France         | Nederlands/Francais
-------------------------------------------------------------------------------

=================
MATRIX-SIG  - SIG on Matrix Math for Python

send messages to: matrix-sig@python.org
administrivia to: matrix-sig-request@python.org
=================