Home , Post

Semi-automatic Fortran 90 to Python conversion

Fortran 90 to Python conversion by string manipulation

Simple Fortran 90 to Python conversion by string manipulations:
1. Replacement of Fortran statements with corresponding Python statements
2. Replacement of math functions
3. Commenting out Fortan-specific statment (e.g. integer, end do)
4. Conversion of do loops to for loops: do j=1, M, 2 => for j in range(0, M, 2)
5. Covertion of array access from ( ) to [ ]
6. Conversion of function headers (possibly multi-line)

The resulting file still require some manual cleanup and formatting.

List of substitution pairs for statements.

In [1]:
map_list=[('!','#'),
    ('\t','    '),
    ('.d0','.0'),
    ('d0','0'),
    ('.eq.',' == '),
    ('.ne.',' != '),
    ('/=',' != '),
    ('.lt.',' < '),
    ('.gt.',' > '),
    ('.le.',' <= '),
    ('.ge.',' >= '),
    ('then',':'),
    ('else if','elif'),
    ('else','else:'),
    ('&','\\'),
    ('.and.',' and '),
    ('.or.',' or '),
    ('.true.',' True '),
    ('.false.',' False '),
    ('end if',''),
    ('do while','while'),
    ('end do',''),
    ('end program','# end program'),
    ('end function','# end function'),
    ('end subroutine','# end subroutine'),
    ('end','# end'),
    ('program','def'),
    ('function','def'),
    ('subroutine','def'),
    ('integer','#integer'),
    ('real','#real'),
    ('allocate','#allocate'),
    ]

List of substitution pairs for math functions (incomplete).

In [2]:
math_list=[
    ('dabs','abs'),
    ('dsqrt','math.sqrt'),
    ('dlog','math.log'),
    ('dexp','math.exp'),
    ]
In [3]:
def file2list(fname, include_eol=False):
    ''' text file to list of string '''
    with open(fname, encoding="utf8") as f:
        if include_eol:
            content = f.readlines() # \n is included
        else:
            content = f.read().splitlines() # \n is not included
        return content
In [4]:
def ireplace(old, new, text):
    ''' Case Insensitive Replace excluding comments
    based on
    http://stackoverflow.com/questions/919056/python-case-insensitive-replace
    ''' 
    idx = 0
    lim = text.find('#')
    if lim < 0:
        lim = len(text)
    while idx < lim:
#     while idx < len(text):
        index_l = text.lower().find(old.lower(), idx)
        if index_l == -1:
            return text
        text = text[:index_l] + new + text[index_l + len(old):]
        idx = index_l + len(old)
    return text
In [5]:
def replace_statements(content,map_list):
    ''' replaice all statements in map_list for all lines '''
    result = list(content) # make a copy
    for p in map_list:
#         result = [l.replace(p[0], p[1]) for l in result] # Case-sensitive
        result = [ireplace(p[0], p[1], l) for l in result]
    return result
In [6]:
def replace_math_functions(content,math_list):
    ''' replaice all math functions in content '''
    for p in math_list:
        content = [ireplace(p[0], p[1], l) for l in content]
    return content
In [7]:
def process_do(line):
    ''' replaice Fortran do to Python for:
        do i=1, N => for i in range(0, N):
        do j=1, M, 2 => for j in range(0, M, 2):
        do iq=ip+1, N => for iq in range(ip+1, N):
    '''
    if line.startswith('#'):
        return line
    if line.strip().startswith( 'do' ):
        if '#' in line:
            do_var_lims,comment = line.split('#')
            comment = ' #'+''.join(comment)
        else:
            do_var_lims,comment = line, ''
        do_var,lims = do_var_lims.split('=')
        for_var = do_var.replace('do','for') + ' in range('
        lims=lims.split(',')
        lims=[i.strip() for i in lims]
        if str(lims[0])=='1':     # heuristic correction for 0-based arrays    
            lims[0]='0'           # warning: this can be unnecessery sometimes.
        result = for_var + ', '.join(lims) + '):'
        if comment:
            result += comment
        return result
    else:
        return line
In [8]:
def replace_all_do(content):
    ''' replaice do statements for all lines '''
    result = [process_do(l) for l in content]
    return result
In [9]:
def adjust_array(line, arr):
    ''' Conversion of Fortran array access to Python:
        line = 'A(i,j) = A(m,A(k,l))'
        arr = 'A'
        result = 'A[i,j] = A[m,A[k,l]]'
    '''
    if line.startswith('#'):
        return line
    i = line.find(arr+'(')
    while i >= 0:
        j = line.find('(',i)
        line = line[:j] + '[' + line[j+1:]
        c = 1
        for k in range(j+1,len(line)):
            if line[k] == '(':
                c += 1
            if line[k] == ')':
                c -= 1
            if c == 0:
                line = line[:k] + ']' + line[k+1:]
#                 i = line.find(arr+'(', k+1) # does not process nested arrays
                i = line.find(arr+'(')
                break
    return line
In [10]:
def adjust_arrays(line):
    ''' adjust all arrays in a line '''
    for a in arrays:
        line = adjust_array(line, a)
    return line
In [11]:
def adjust_all_arrays(content):
    ''' adjust all arrays in all lines '''
    result = [adjust_arrays(l) for l in content]
    return result
In [12]:
def adjust_functions(content):
    """ Adds ':' after ')' """    
    for n,line in enumerate(content):
        count = 0
        if line.strip().startswith('def'):
            i = line.find('(')
            if i >= 0:
                count = 1
                for k in range(i,len(line)):
                    #print(k, line[k])
                    if line[k] == '#':
                        break
                    if line[k] == ')':
                        count = 0
                        content[n] = line[:k+1] + ':' + line[k+1:]
                        break
            else:
                count = 0
                i = line.find('#') 
                if i >= 0:
                    content[n] =  line[:i] + ':' + line[i:]
                else:
                    content[n] =  line + ':'
        else:
            if count > 0:
                i = 0
                for k in range(i,len(line)):
                    if line[k] == '#':
                        break
                    if line[k] == ')':
                        count = 0
                        content[n] = line[:k+1] + ':' + line[k+1:]
                        break
    return content
In [13]:
def list2file(fname, output):
    ''' list of string to text file '''
    with open(fname,'w', encoding="utf8") as out:
        for line in output:
            out.write(line+'\n')
In [14]:
def f90_to_py(content,map_list,math_list,arrays):
    ''' Fortran 90 to Python conversion 
        by string manipulations
        content - input list of f90 file content strings
        map_list - list of substitution pairs, 
                   e.g. ('.lt.',' < '),('DABS','abs'),
        output - list of python file content strings
    '''
    output = replace_statements(content,map_list)
    output = replace_math_functions(output,math_list)
    output = replace_all_do(output)
    output = adjust_all_arrays(output)
    output = adjust_functions(output)
    return output
In [15]:
fname='ujacobi.f90'
In [16]:
foutname='ujacobi.py'

List of arrays

In [17]:
arrays=['A','D','V','B','Z']

Read Fortran file

In [18]:
content = file2list(fname)

Generaqte conversion

In [19]:
output = f90_to_py(content,map_list,math_list,arrays)

Write Python file

In [20]:
list2file(foutname, output)

Home , Post