Exemplo n.º 1
0
def convert(input_file, gen_dir, mech_dir, mcm_dir):
    """
    This is the main function of this file. It takes as input an MCM file, and from it generates
    5 files for use by AtChem2's Fortran code:

    - 'Generic Rate Coefficients' and 'Complex reactions' go to gen_dir/mechanism.f90 with little more than formatting
      changes - each line is replicated in full but with each named rate converted to an element in the vector q.
    - The rates defined in 'Reaction definitions' also go to gen_dir/mechanism.f90 as elements of the vector p.
    - The species involved as reactants (respectively products) in reactions in 'Reaction definitions' are split up into individual
      species, and their species and reactions numbers go to mechanism.reac (respectively mechanism.prod). Combining
      mechanism.reac, mechanism.prod and the last section of gen_dir/mechanism.f90 gives the original information
      contained in 'Reaction definitions' but in the format that AtChem2 can parse.
    - The numbers and names of all species encountered go to mechanism.species.
    - The numbers and names of all RO2 species in 'Peroxy radicals' end up in mechanism.ro2.

    :param input_file: string containing a relative or absolute reference to the mcm file to be processed.
    :param gen_dir: string containing a relative or absolute reference to the directory in which the function should
      place mechanism.f90. This is normally model/configuration.
    :param mech_dir: string containing a relative or absolute reference to the directory in which the function should
      place mechanism.{prod,reac,ro2,species}. This is normally model/configuration/ for the given model.
    :param mcm_dir: string containing a relative or absolute reference to the directory housing the reference file peroxy-radicals_v3.3.1.
      This is normally mcm/
    """
    # Work out the values of directory and filename of input_file, and check their existence.
    input_directory = os.path.dirname(os.path.abspath(input_file))
    input_filename = os.path.basename(input_file)
    assert os.path.isfile(os.path.join(input_directory, input_filename)), 'The input file ' + str(
        os.path.join(input_directory, input_filename)) + ' does not exist.'
    print input_directory

    # Fix the input contents of any errant newlines
    fix_mechanism_fac.fix_fac_full_file(os.path.join(input_directory, input_filename))

    # Read in the input file
    print 'Reading input file'
    with open(os.path.join(input_directory, input_filename), 'r') as input_file:
        s = input_file.readlines()

    # split the lines into the following sections:
    # - Ignore everything up to Generic Rate Coefficients
    # - Generic Rate Coefficients
    # - Complex reactions
    # - Peroxy radicals
    # - Reaction definitions
    section_headers_indices = [0, 1, 2, 3]
    section_headers = ['Generic Rate Coefficients', 'Complex reactions', 'Peroxy radicals', 'Reaction definitions']
    generic_rate_coefficients = []
    complex_reactions = []
    peroxy_radicals = []
    reaction_definitions = []

    section = 0
    for line in s:
        for header_index in section_headers_indices:
            if section_headers[header_index] in line:
                section += 1
        if section == 1:
            generic_rate_coefficients.append(line)
        elif section == 2:
            complex_reactions.append(line)
        elif section == 3:
            peroxy_radicals.append(line)
        elif section == 4:
            reaction_definitions.append(line)
        else:
            assert section == 0, "Error, section is not in [0,4]"


    # Convert peroxy_radicals to a list of strings, each of the RO2 species from 'Peroxy radicals'
    ro2List = []
    for item in peroxy_radicals:
        if not re.match('\*', item):
            # We have an equals sign on the first line. Handle this by splitting against =, then taking the last element of the
            # resulting list, which will either be the right-hand side of the first line, or the whole of any other line.
            # Similarly, the final line will end with a colon. Handle in a similar way.
            # Then split by +. Append each item to ro2_input: multiple appends use 'extend'
            ro2List.extend([elem.strip() for elem in item.split('=')[-1].split(';')[0].strip().split('+')])
    # Remove empty strings
    ro2List = filter(None, ro2List)


    # Read in the reference RO2 species from the peroxy-radicals_v3.3.1 file
    with open(os.path.join(mcm_dir, 'peroxy-radicals_v3.3.1'), 'r') as RO2List_file:
        RO2List_reference = [r.rstrip() for r in RO2List_file.readlines()]

    # Check each of the RO2s from 'Peroxy radicals' are in the reference RO2 list. If not print a warning at the top of
    # mechanism.f90 for each errant species.
    # TODO: This will break the exected format when mechanism.f90 is replaced by a parsable format.
    print 'looping over inputted ro2s'

    with open(os.path.join(gen_dir, 'mechanism.f90'), 'w') as mech_rates_file:
        mech_rates_file.write("""! Note that this file is generated by tools/mech_converter.py
! based upon the file tools/mcm_example.fac
! Any manual edits to this file will be overwritten when
! calling tools/mech_converter.py

""")
        for ro2_species in [element for  element in ro2List if element not in RO2List_reference]:
            print ' ****** Warning: ' + ro2_species + ' NOT found in RO2List ****** '
            mech_rates_file.write('! ' + ro2_species +
                                  ' is not in the MCM list of RO2 species. Should it be in the RO2 sum?\n')





    # Initialise list, dictionary and a counter.
    mechanism_rates_coeff_list = []
    variablesDict = dict()
    reactionNumber = 0
    # Process sections 1 and 2
    # - copy comment lines across
    # - other lines are reformatted to be Fortran syntax, then its contents edited to convert individual
    #   rate names to elements in a vector q.
    for line in generic_rate_coefficients + complex_reactions:
        # Check for comments (beginning with a !), or blank lines
        if (re.match('!', line) is not None) or (line.isspace()):
            mechanism_rates_coeff_list.append(line)
        # Check for lines starting with either ; or *, and write these as comments
        elif (re.match(';', line) is not None) or (re.match('[*]', line) is not None):
            mechanism_rates_coeff_list.append('!' + line)
        # Otherwise assume all remaining lines are in the correct format, and so process them
        else:
            # reactionNumber keeps track of the line we are processing
            reactionNumber += 1

            # This matches anything like @-dd.d and replaces with **(-dd.d). This uses (?<=@) as a lookbehind assertion,
            # then matches - and any combination of digits and decimal points. This replaces the negative number by its
            # bracketed version.
            # It also then converts all @ to ** etc.
            line2 = re.sub('(?<=@)-[0-9.]*',
                           '(\g<0>)',
                           line.replace(';', '').strip()
                           ).replace('@', '**')
            # Append _DP to the end of all digits that aren't followed by more digits or letters (targets a few too many)
            line2 = re.sub('[0-9]+(?![a-zA-Z0-9\.])',
                           '\g<0>_DP',
                           line2)
            # Undo the suffix _DP for any species names and for LOG10
            line2 = re.sub(r'\b(?P<speciesnames>[a-zA-Z][a-zA-Z0-9]*)_DP',
                           '\g<speciesnames>',
                           line2)
            # Undo the suffix _DP for any numbers like 1D7 or 2.3D-8
            line2 = re.sub(r'\b(?P<doubles1>[0-9][0-9\.]*)[dDeE](?P<doubles2>[+-]*[0-9]+)_DP',
                           '\g<doubles1>e\g<doubles2>_DP',
                           line2)
            # Add .0 to any literals that don't have a decimal place - this is necessary as it seems you can't use extended
            # precision on such a number - gfortran complains about an unknown integer kind, when it should really be a real kind
            line2 = re.sub(r'(?<![\.0-9+-dDeE])(?P<doubles>[0-9]+)_DP',
                           '\g<doubles>.0_DP',
                           line2)

            # strip whitespace, ; and %
            cleaned_line = line2.strip().strip('%;').strip()

            # Process the assignment: split by = into variable names and values
            [lhs, rhs] = re.split('=', cleaned_line)

            # Strip each.
            variable_name = lhs.strip()
            value = rhs.strip()

            # TODO: check for duplicates
            variablesDict[variable_name] = reactionNumber

            # Replace any variables declared here with references to q, with each new variable assigned
            # to a new element of q.
            new_rhs = tokenise_and_process(value, variablesDict)

            # Save the resulting string to mechanism_rates_coeff_list
            mechanism_rates_coeff_list.append('q('+str(variablesDict[variable_name]) + ') = ' + new_rhs + '  !' + cleaned_line + '\n')

    # Save the number of such equations to be output to mechanism.{prod,reac}
    numberOfGenericComplex = reactionNumber




    # Initialise a few variables
    speciesList = []
    rateConstants = []
    reactionNumber = 0
    # Process 'Reaction definitions'. We process this before 'Peroxy radicals' because that relies on our
    # SpeciesList generated here.
    # - copy comment lines across
    # - other lines are split into their consituent parts:
    #   - rateConstants are the reaction rates - these are processed via reformatting and tokenisation to use the vector q where needed.
    #   - the reactants and products of each species are collected up, numbered as necessary, and their placements output to mechanism.{prod,reac,species}
    mech_reac_list = []
    mech_prod_list = []
    # Loop over all lines in the reaction_definitions section of the input file
    for line in reaction_definitions:

        # Check for comments (beginning with a !), or blank lines
        if (re.match('!', line) is not None) or (line.isspace()):
            rateConstants.append(line)
        # Check for lines starting with either ; or *, and write these as comments
        elif (re.match(';', line) is not None) or (re.match('[*]', line) is not None):
            rateConstants.append('!' + line)
        # Otherwise assume all remaining lines are in the correct format, and so process them
        else:
            # reactionNumber keeps track of the line we are processing
            reactionNumber += 1

            # strip whitespace, ; and %
            line = line.strip().strip('%;').strip()

            # split by the semi-colon : lhs is reaction rate, rhs is reaction equation
            [lhs, rhs] = re.split(':', line)

            # Add reaction rate to rateConstants
            rateConstants.append(lhs)

            # Process the reaction: split by = into reactants and products
            [reactantsList, productsList] = re.split('=', rhs)

            # Process each of reactants and products by splitting by +. Strip each at this stage.
            reactants = [item.strip() for item in re.split('[+]', reactantsList)]
            products = [item.strip() for item in re.split('[+]', productsList)]

            # Ignore empty reactantsList
            if not reactantsList == '':
                # Compare each reactant against known species.
                reactantNums = []
                for x in reactants:
                    # If the reactant is a known species then add its number to reactantNums
                    if x in speciesList:
                        reactantNums.append(speciesList.index(x)+1)
                    else:
                        # Reactant x is not a known species.
                        # Add reactant to speciesList, and add this number to
                        # reactantNums to record this reaction.
                        speciesList.append(x)
                        reactantNums.append(len(speciesList))

                # Write the reactants to mech_reac_list
                mech_reac_list.extend([str(reactionNumber) + ' ' + str(z) + '\n' for z in reactantNums])

            if not productsList == '':
                # Compare each product against known species.
                productNums = []
                for x in products:
                    # If the reactant is a known species then add its number to reactantNums
                    if x in speciesList:
                        productNums.append(speciesList.index(x)+1)
                    else:
                        # Product x is not a known species.
                        # Add product to speciesList, add this number to
                        # productNums to record this reaction.
                        speciesList.append(x)
                        productNums.append(len(speciesList))

                # Write the products to mechanism.prod
                mech_prod_list.extend([str(reactionNumber) + ' ' + str(z) + '\n' for z in productNums])

    with open(os.path.join(mech_dir, 'mechanism.prod'), 'w') as prod_file:
        # Output number of species and number of reactions
        prod_file.write(str(len(speciesList)) + ' ' + str(reactionNumber) + ' ' + str(numberOfGenericComplex) + ' numberOfSpecies numberOfReactions numberOfGenericComplex\n')
        # Write all other lines
        for line in mech_prod_list:
            prod_file.write(line)

    with open(os.path.join(mech_dir, 'mechanism.reac'), 'w') as reac_file:
        # Output number of species and number of reactions
        reac_file.write(str(len(speciesList)) + ' ' + str(reactionNumber) + ' ' + str(numberOfGenericComplex) + ' numberOfSpecies numberOfReactions numberOfGenericComplex\n')
        # Write all other lines
        for line in mech_reac_list:
            reac_file.write(line)

    # Write speciesList to mechanism.species, indexed by (1 to len(speciesList))
    with open(os.path.join(mech_dir, 'mechanism.species'), 'w') as species_file:
        for i, x in zip(range(1, len(speciesList) + 1), speciesList):
            species_file.write(str(i) + ' ' + str(x) + '\n')


    # Write out rate coefficients
    i = 0
    mech_rates_list = []
    for rate_counter, x in zip(range(len(s)), rateConstants):
        if (re.match('!', x) is not None) | (x.isspace()):
            mech_rates_list.append(str(x))
        else:
            # This matches anything like @-dd.d and replaces with **(-dd.d). This uses (?<=@) as a lookbehind assertion,
            # then matches - and any combination of digits and decimal points. This replaces the negative number by its
            # bracketed version.
            i += 1
            string = re.sub('(?<=@)-[0-9.]*', '(\g<0>)', x)
            # Now convert all @ to ** etc.
            string = string.replace('@', '**')
            string = string.replace('<', '(')
            string = string.replace('>', ')')
            # Replace any float-type numbers (xxx.xxxE+xx) with double-type - (xxx.xxxD+xx)
            string = re.sub(r'(?P<single>[0-9]+\.[0-9]+)[eE]',
                           '\g<single>D',
                           string)
            mech_rates_list.append('p(' + str(i) + ') = ' + \
              tokenise_and_process(string, variablesDict) + '  !' + reaction_definitions[rate_counter])


    # # Combine mechanism rates and RO2 sum files
    with open(os.path.join(gen_dir, 'mechanism.f90'), 'a') as mech_rates_coeff_file:
        mech_rates_coeff_file.write("""
module mechanism_mod
    use, intrinsic :: iso_c_binding
contains

    subroutine update_p(p, q, TEMP, N2, O2, M, RH, H2O, DEC, BLHEIGHT, DILUTE, JFAC, ROOFOPEN, J, RO2) bind(c,name='update_p')
        implicit none

        integer, parameter :: DP = selected_real_kind( p = 15, r = 307 )
           real(c_double), intent(inout) :: p(:), q(:)
        real(c_double), intent(in) :: TEMP, N2, O2, M, RH, H2O, DEC, BLHEIGHT, DILUTE, JFAC, ROOFOPEN, J(:), RO2
        """)
# Write out Generic Rate Coefficients and Complex reactions
        for item in mechanism_rates_coeff_list:
            mech_rates_coeff_file.write(item)
        # Write out Reaction definitions
        for r in mech_rates_list:
            mech_rates_coeff_file.write(r)
        mech_rates_coeff_file.write("""
    end subroutine update_p
end module mechanism_mod
""")




    # Finally, now that we have the full species list, we can output the RO2s to mechanism.ro2
    # loop over RO2 and write the necessary line to mechanism.ro2, using the species number of the RO2
    print 'adding RO2 to model/configuration/mechanism.ro2'
    with open(os.path.join(mech_dir, 'mechanism.ro2'), 'w') as ro2_file:
        ro2_file.write("""! Note that this file is generated by tools/mech_converter.py based upon the file tools/mcm_example.fac. Any manual edits to this file will be overwritten when calling tools/mech_converter.py
""")

        for ro2List_i in ro2List:
            for speciesNumber, y in zip(range(1, len(speciesList) + 1), speciesList):
                if ro2List_i.strip() == y.strip():
                    ro2_file.write(str(speciesNumber) + ' !' + ro2List_i.strip() + '\n')
                    # Exit loop early if species found
                    break
            # This code only executes if the break is NOT called, i.e. if the loop runs to completion without the RO2 being
            # found in the species list
            else:
                ro2_file.write('0 ! error RO2 not in mechanism: ' + ro2List_i + '\n')
Exemplo n.º 2
0
def convert(input_file):
    script_directory = os.path.dirname(os.path.abspath(__file__))
    input_directory = os.path.dirname(os.path.abspath(input_file))
    input_filename = os.path.basename(input_file)
    assert os.path.isfile(os.path.join(
        input_directory, input_filename)), 'The input file ' + str(
            os.path.join(input_directory, input_filename)) + ' does not exist.'
    print input_directory
    # Fix the input contents of any errant newlines
    fix_mechanism_fac.fix_fac_full_file(
        os.path.join(input_directory, input_filename))

    # Read in the input file
    print 'Reading input file'
    with open(os.path.join(input_directory, input_filename),
              'r') as input_file:
        s = input_file.readlines()
        # print s

    # split the lines into the following sections:
    # - Ignore everything up to Generic Rate Coefficients
    # - Generic Rate Coefficients
    # - Complex reactions
    # - Peroxy radicals
    # - Reaction definitions
    section_headers_indices = [0, 1, 2, 3]
    section_headers = [
        'Generic Rate Coefficients', 'Complex reactions', 'Peroxy radicals',
        'Reaction definitions'
    ]
    generic_rate_coefficients = []
    complex_reactions = []
    peroxy_radicals = []
    reaction_definitions = []

    section = 0
    for line in s:
        for header_index in section_headers_indices:
            if section_headers[header_index] in line:
                section += 1
        if section == 1:
            # print line
            generic_rate_coefficients.append(line)
        elif section == 2:
            # print line
            complex_reactions.append(line)
        elif section == 3:
            # print line
            peroxy_radicals.append(line)
        elif section == 4:
            # print line
            reaction_definitions.append(line)
        else:
            assert section == 0, "Error, section is not in [0,4]"
            # print line

    # print 'generic_rate_coefficients'
    # print generic_rate_coefficients
    # print 'complex_reactions'
    # print complex_reactions
    # print 'peroxy_radicals'
    # print peroxy_radicals
    # print 'reaction_definitions'
    # print reaction_definitions
    # Initialise a few variables
    speciesList = []
    rateConstants = []
    reactionNumber = 0

    with open(os.path.join(input_directory, 'mechanism.reactemp'),
              'w') as reac_temp_file, open(
                  os.path.join(input_directory, 'mechanism.prod'),
                  'w') as prod_file:
        # Loop over all lines in the reaction_definitions section of the input file
        for line in reaction_definitions:

            # Check for comments (beginning with a !), or blank lines
            if (re.match('!', line) is not None) | (line.isspace()):
                rateConstants.append(line)
            # Check for lines starting with either ; or *, and write these as comments
            elif (re.match(';', line) is not None) | (re.match('[*]', line)
                                                      is not None):
                rateConstants.append('!' + line)
            # Otherwise assume all remaining lines are in the correct format, and so process them
            else:
                # reactionNumber keeps track of the line we are processing
                reactionNumber += 1
                # print 'line =', line
                # strip whitespace, ; and %
                line = line.strip().strip('%').strip(';').strip()

                print ''
                print 'line =', line
                # split by the semi-colon : a[0] is reaction rate, a[1] is reaction equation
                a = re.split(':', line)
                # print 'a = ', a

                # Add reaction rate to rateConstants
                rateConstants.append(a[0])
                # print 'rate =', a[0]

                # Process the reaction: split by = into reactants and products
                # print 'reaction = ', a[1]

                reaction_parts = re.split('=', a[1])
                # print reaction_parts

                reactantsList = reaction_parts[0]
                productsList = reaction_parts[1]

                # print 'reactantlist = ', reactantsList
                # print 'productlist = ', productsList

                # Process each of reactants and products by splitting by +. Strip each at this stage.
                reactants = [
                    item.strip() for item in re.split('[+]', reactantsList)
                ]
                products = [
                    item.strip() for item in re.split('[+]', productsList)
                ]

                # print 'reactants =', reactants
                # print 'products =', products

                # Ignore empty reactantsList
                if not reactantsList == '':
                    # Compare each reactant against known species.
                    reactantNums = []
                    for x in reactants:
                        j = 0
                        for y in speciesList:
                            # Check for equality: if equality, then the reactant is a known species and its number should be
                            # added to the reactantNums variable
                            if x == y:
                                reactantNums.append(j + 1)
                                # print 'found:', y + ', j =', j
                                break
                            j += 1
                        # This code only executes if the break is NOT called, i.e. if the loop runs to completion without
                        # the reactant being found in the known species
                        else:
                            # Add reactant to speciesList, and add this number to
                            # reactantNums to record this reaction.
                            speciesList.append(x)
                            reactantNums.append(len(speciesList))
                            print 'adding', x, 'to speciesList'

                    # Write the reactants to mechanism.reactemp
                    for z in reactantNums:
                        reac_temp_file.write(
                            str(reactionNumber) + ' ' + str(z) + '\n')

                if not productsList == '':
                    # Compare each product against known species.
                    productNums = []
                    for x in products:
                        j = 0
                        for y in speciesList:
                            # Check for equality: if equality, then the product is a known species and its number should be
                            # added to the productNums variable
                            if x == y:
                                productNums.append(j + 1)
                                # print 'found:', y + ', j =', j
                                break
                            j += 1
                        # This code only executes if the break is NOT called, i.e. if the loop runs to completion without
                        # the product being found in the known species
                        else:
                            # Add product to speciesList, add this number to
                            # productNums to record this reaction.
                            speciesList.append(x)
                            productNums.append(len(speciesList))
                            print 'adding', x, 'to speciesList'

                    # Write the products to mechanism.prod
                    for z in productNums:
                        prod_file.write(
                            str(reactionNumber) + ' ' + str(z) + '\n')

        # Mark end of file with zeros
        reac_temp_file.write('0\t0\t0\t0 \n')
        prod_file.write('0\t0\t0\t0')
        # Output number of species and number of reactions
        reac_temp_file.write(
            str(len(speciesList)) + ' ' + str(reactionNumber) +
            ' numberOfSpecies numberOfReactions\n')

    # Copy mechanism.reactemp to mechanism.reac in a different order to make it readable by the model (move the last line to
    # the first line).
    with open(os.path.join(input_directory,
                           'mechanism.reactemp')) as reac_temp_file, open(
                               os.path.join(input_directory, 'mechanism.reac'),
                               'w') as reac_file:
        st = reac_temp_file.readlines()
        # Write last line
        reac_file.write(st[len(st) - 1])
        # Write all other lines
        for line in st[:-1]:
            reac_file.write(line)

    # Write speciesList to mechanism.species, indexed by (1 to len(speciesList))
    with open(os.path.join(input_directory, 'mechanism.species'),
              'w') as species_file:
        for i, x in zip(range(1, len(speciesList) + 1), speciesList):
            species_file.write(str(i) + ' ' + str(x) + '\n')

    # Write out rate coefficients
    i = 1
    with open(
            os.path.join(input_directory, 'mechanism-rate-coefficients.ftemp'),
            'w') as mech_rates_temp_file:
        for rate_counter, x in zip(range(len(s)), rateConstants):
            if (re.match('!', x) is not None) | (x.isspace()):
                mech_rates_temp_file.write(str(x))
            else:
                # This matches anything like @-dd.d and replaces with **(-dd.d). This uses (?<=@) as a lookbehind assertion,
                # then matches - and any combination of digits and decimal points. This replaces the negative number by its
                # bracketed version.
                string = re.sub('(?<=@)-[0-9.]*', '(\g<0>)', x)
                # Now convert all @ to ** etc.
                string = string.replace('@', '**')
                string = string.replace('<', '(')
                string = string.replace('>', ')')
                mech_rates_temp_file.write('  p(' + str(i) + ') = ' + string +
                                           '  !' +
                                           reaction_definitions[rate_counter])
                i += 1

    # Write RO2 data to file
    in_RO2_lines = False
    ro2_input = []
    for item in peroxy_radicals:
        if not in_RO2_lines:
            # Check to see whether we are entering the 'Reaction definitions' section
            if 'RO2 = ' in item:
                in_RO2_lines = True
        if in_RO2_lines:
            if not re.match('\*', item):
                ro2_input.append(item)
            else:
                in_RO2_lines = False

    ro2List = []
    for l in ro2_input:
        # We have an equals sign on the first line. Handle this by splitting against =, then taking the last element of the
        # resulting list, which will either be the right-hand side of the first line, or the whole of any other line.
        # Then split by +.
        strArray = l.split('=')[-1].split('+')

        # print strArray
        # For each element, remove any semi-colons, strip, and then append if non-empty.
        for x in strArray:
            x = x.replace(';', '').strip()
            if x == '':
                pass
                # print 'doing nothing'
            else:
                # print x
                ro2List.append(x)

    # check RO2s are in RO2 list
    with open(os.path.join(script_directory, 'RO2listv3.3.1')) as RO2List_file:
        RO2List_input = RO2List_file.readlines()

    for r in RO2List_input:
        r = r.strip()
        # print r

    # Check that each species is in the RO2 list. If so, just print to screen. Otherwise, print a warning at the top of
    # mechanism-rate-coefficients.f90 for each errant species.
    print 'looping over inputted ro2s'
    # print 'The RO2List is: ', ro2List

    with open(os.path.join(input_directory, 'mechanism-rate-coefficients.f90'),
              'w') as mech_rates_file:
        mech_rates_file.write(
            """! Note that this file is generated by tools/mech_converter.py,
! based upon the file tools/mcm_subset.fac. Any manual edits to this file will be overwritten
! when calling tools/mech_converter.py
""")
        for ro2List_i in ro2List:
            for ro2List_input_j in RO2List_input:
                if ro2List_i.strip() == ro2List_input_j.strip():
                    # print ro2List_i.strip() + ' found in RO2List'
                    break
            # This code only executes if the break is NOT called, i.e. if the loop runs to completion without the species
            # being found in the RO2 list
            else:
                print ' ****** Warning: ' + ro2List_i.strip(
                ) + ' NOT found in RO2List ****** '
                mech_rates_file.write(
                    '! ' + ro2List_i.strip() +
                    ' is not in the MCM list of RO2 species. Should it be in the RO2 sum?\n'
                )

        # loop over RO2 and write the necessary line to mechanism-rate-coefficients.f90, using the species number of the RO2
        mech_rates_file.write('  ro2 = 0.00e+00\n')
        print 'adding RO2 to mechanism-rate-coefficients.f90'
        for ro2List_i in ro2List:
            # print 'ro2List_i: ' + ro2List_i
            for speciesNumber, y in zip(range(1,
                                              len(speciesList) + 1),
                                        speciesList):
                if ro2List_i.strip() == y.strip():
                    mech_rates_file.write('  ro2 = ro2 + y(' +
                                          str(speciesNumber) + ')!' +
                                          ro2List_i.strip() + '\n')
                    # Exit loop early if species found
                    break
            # This code only executes if the break is NOT called, i.e. if the loop runs to completion without the RO2 being
            # found in the species list
            else:
                mech_rates_file.write('\t ! error RO2 not in mechanism: ' +
                                      ro2List_i + '\n')

        mech_rates_file.write('\n\n')
        # Read in NOY data, which is arrange as 'NOY = blah + blah + blah \n + blah + blah ;'
        # with open('./NOY.fac') as NOY_fac_file
        #   NOY_input = NOY_fac_file.readlines()
        #
        # NOYList = []
        # for n in NOY_input:
        # # We have an equals sign on the first line. Handle this by splitting against =, then taking the last element of the
        # # resulting list, which will either be the right-hand side of the first line, or the whole of any other line.
        # # Then split by +
        #   strArray = n.split('=')[-1].split('+')
        #
        #	print strArray
        # # For each element, remove any semi-colons, strip, and then append if non-empty.
        #	for x in strArray:
        #		x = x.replace(';', '').strip()
        #		if x == '':
        #			print 'doing nothing'
        #		else:
        #			print x
        #			NOYList.append(x)
        #
        # # loop over NOY and write the necessary line to mechanism-rate-coefficients.f90, using the species number of the NOY
        # mech_rates_file.write('\tNOY = 0.00e+00\n')
        # for NOYList_i in NOYList:
        #	print 'NOYList_i: ' + NOYList_i
        #	for speciesNumber, y in zip(range(1, len(speciesList)+1), speciesList):
        #		if NOYList_i.strip() == y.strip():
        #			mech_rates_file.write('  NOY = NOY + y(' + str(speciesNumber) + ')!' + NOYList_i.strip() + '\n')
        #			# Exit loop early if species found
        #		    break
        # # This code only executes if the break is NOT called, i.e. if the loop runs to completion without the NOY being
        # # found in the species list
        #   else:
        #		mech_rates_file.write('\t !error NOY not in mechanism: ' + NOYList_i + '\n')
        #
        # mech_rates_file.write('\n\n')
        # # Combine mechanism rates and RO2 / NOY sum files
        with open(
                os.path.join(input_directory,
                             'mechanism-rate-coefficients.ftemp')
        ) as mech_rates_temp_file:
            rs = mech_rates_temp_file.readlines()

        for r in rs:
            mech_rates_file.write(r)

    os.remove(os.path.join(input_directory, 'mechanism.reactemp'))
    os.remove(
        os.path.join(input_directory, 'mechanism-rate-coefficients.ftemp'))

    mechanism_rates_list = [
        """
! Note that this file is generated by tools/mech_converter.py,
! based upon the file tools/mcm_subset.fac. Any manual edits to this file will be overwritten
! when calling tools/mech_converter.py

SUBROUTINE mechanism_rates (p, t, y, mnsp)
  USE photolysisRates
  USE zenithData1
  USE constraints
  USE envVars, ONLY: ro2

  IMPLICIT NONE

  ! calculates rate constants from arrhenius information
  DOUBLE PRECISION, INTENT (out) :: p(*)
  DOUBLE PRECISION, INTENT (in) :: t
  INTEGER, INTENT (in) :: mnsp
  DOUBLE PRECISION, INTENT (in) :: y(mnsp)
  DOUBLE PRECISION :: temp, pressure, dummy
"""
    ]
    mechanism_rates_list.append("""
  ! declare variables missed in MCM definition
  INTEGER :: i
  DOUBLE PRECISION :: photoRateAtT

  INCLUDE 'modelConfiguration/mechanism-rate-declarations.f90'

  CALL ro2sum (ro2, y)
  dummy = y(1)

  dec = -1e16

  CALL getEnvVarsAtT (t, temp, rh, h2o, dec, pressure, m, blh, dilute, jfac, roofOpen)

  CALL atmosphere (o2, n2, m)

  !O2 = 0.2095*m
  !N2 = 0.7809*m

  ! * **** SIMPLE RATE COEFFICIENTS *****                     *""")
    coeffSpeciesList = [
        'N2', 'O2', 'M', 'RH', 'H2O', 'DEC', 'BLH', 'DILUTE', 'JFAC',
        'ROOFOPEN'
    ]
    reactionNumber = 0
    # P
    for line in generic_rate_coefficients + complex_reactions:
        # Check for comments (beginning with a !), or blank lines
        if (re.match('!', line) is not None) | (line.isspace()):
            mechanism_rates_list.append('  ' + line)
        # Check for lines starting with either ; or *, and write these as comments
        elif (re.match(';', line) is not None) | (re.match('[*]', line)
                                                  is not None):
            mechanism_rates_list.append('  !' + line)
        # Otherwise assume all remaining lines are in the correct format, and so process them
        else:
            # This matches anything like @-dd.d and replaces with **(-dd.d). This uses (?<=@) as a lookbehind assertion,
            # then matches - and any combination of digits and decimal points. This replaces the negative number by its
            # bracketed version.
            # It also then converts all @ to ** etc.
            # Save the resulting string to mechanism_rates_list
            mechanism_rates_list.append(
                '  ' +
                re.sub('(?<=@)-[0-9.]*', '(\g<0>)',
                       line.replace(';', '').strip()).replace('@', '**') +
                '\n')

            # Now we need to find the list of all species that are used in these equations, so we can declare them
            # at the top of the Fortran source file.

            # reactionNumber keeps track of the line we are processing
            reactionNumber += 1
            # print 'line =', line
            # strip whitespace, ; and %
            line = line.strip().strip('%').strip(';').strip()
            print 'line =', line
            # split by the semi-colon : a[0] is reaction rate, a[1] is reaction equation
            a = line
            # print 'a = ', a

            # Add reaction rate to rateConstants
            # rateConstant = a[0]
            # rateConstants.append(rateConstant)
            # print rateConstant

            # Process the reaction: split by = into reactants and products
            # print 'reaction =', a

            reaction_parts = re.split('=', a)
            # print reaction_parts

            LHSList = reaction_parts[0]
            RHSList = reaction_parts[1]

            # print 'reactantlist = ', LHSList
            # print 'productlist = ', RHSList

            # Process each of reactants and products by splitting by +. Strip each at this stage.
            reactant = LHSList.strip(
            )  # [item.strip() for item in re.split('[+]', LHSList)]
            products = RHSList.strip(
            )  # [item.strip() for item in re.split('[+]', RHSList)]

            # print 'reactants = ', reactants
            # print 'products = ', products

            # Compare reactant against known species.
            if reactant in coeffSpeciesList:
                pass
                # print 'found:', reactant
            else:
                # Add reactant to coeffSpeciesList, and add this number to
                # reactantNums to record this reaction.
                coeffSpeciesList.append(reactant)
                print 'adding', reactant, 'to coeffSpeciesList'

            if not RHSList.isspace():
                # Compare each product against known species.
                productNums = []
                # print RHSList
                # Replace all math characters and brackets with spaces, and split the remaining string by spaces.
                # Now, each string in the sublist will:
                # - start with a digit
                # - be a 'reserved word' i.e. LOG10, EXP, TEMP, PRESSURE
                # - otherwise, be a species
                RHSList_sub = re.sub('[()\-+*@/]', ' ', RHSList).split(' ')
                # print RHSList_sub
                RHSList_sub = [item.upper() for item in RHSList_sub]
                for x in RHSList_sub:
                    # Filter out spaces, numbers, and maths symbols
                    if (not re.match('[0-9]', x)) and (not x == ''):
                        # Filter out our 'reserved words'
                        if not any(x == reserved for reserved in
                                   ['EXP', 'TEMP', 'PRESSURE', 'LOG10', 'T']):
                            # print x
                            if x in coeffSpeciesList:
                                pass
                                # print 'found: ', x
                            else:
                                coeffSpeciesList.append(x)
                                print 'adding', x, 'to coeffSpeciesList'

    # Recombine the species found into lines of 10 in the right format to declare them as Fortran variables.
    # Begin wthe first line as necessary
    newline = '  DOUBLE PRECISION ::'
    mechanism_rates_decl = []
    # Loop over all species
    for i, item in zip(range(1, len(coeffSpeciesList) + 1), coeffSpeciesList):
        # Add the next species
        newline += ' ' + item.strip()
        # If it's the last species, then exit the loop with some extra newlines
        if i == len(coeffSpeciesList):
            mechanism_rates_decl.append(newline + '\n\n\n')
            continue
        # Otherwise, every tenth species gets rounded off with a newline and a prefix to the next line
        if i % 10 == 0:
            mechanism_rates_decl.append(newline + '\n')
            newline = '  DOUBLE PRECISION ::'
        else:
            # If not, add a spacer
            newline += ','

    # Insert the list generated above into the right place in the master list
    mechanism_rates_list = list(
        mechanism_rates_list[0]) + mechanism_rates_decl + list(
            mechanism_rates_list[1:])

    mechanism_rates_list.append("""  DO i = 1, nrOfPhotoRates
     IF (useConstantValues==0) THEN
        IF (cosx<1.00d-10) THEN
           j(ck(i)) = 1.0d-30
        ELSE
           j(ck(i)) = cl(i)*cosx**(cmm(i))*EXP(-cnn(i)*secx)*transmissionFactor(i)*roofOpen*jfac
        ENDIF
     ELSE
        j(ck(i)) = cl(i)
     ENDIF
  ENDDO

  DO i = 1, numConPhotoRates
     CALL getConstrainedQuantAtT2D (t, photoX, photoY, photoY2, photoNumberOfPoints(i), photoRateAtT, 2, i, &
          maxNumberOfDataPoints, numConPhotoRates)
     j(constrainedPhotoRatesNumbers(i)) = photoRateAtT
  ENDDO

  INCLUDE 'modelConfiguration/mechanism-rate-coefficients.f90'
  RETURN
END SUBROUTINE mechanism_rates

INCLUDE 'modelConfiguration/extraOutputSubroutines.f90'
""")
    # print mechanism_rates_list
    with open(os.path.join(script_directory, '../mechanism-rates.f90'),
              'w+') as mr2_file:
        for item in mechanism_rates_list:
            mr2_file.write(item)