# Print for debugging purposes if doprint: printout('Max interation not met. Starting next iteration...\n') # ============== Stop the loop, max iteration reached ============== # # Stop if max iteration is reached else: # Print to screen printout('Maximum iteration reached, ending minimization.\n') # Set repeat to False to stop the loop repeat = False # Calculate delta values delta = x_new - x # Calculate delta_bar values delta_bar = x_bar_new - x_bar # Name output files with corresponding iteration number name file_results = datadirr + '/results-machine-read.txt' file_fancyResults = datadirr + '/results-visual.txt' # Export all values into machine and human readable output files form.output(datadirr, header, it_num, speclist, x, x_new, delta, \ x_bar, x_bar_new, delta_bar, file_results, doprint) form.fancyout_results(datadirr, header, it_num, speclist, x, x_new, \ delta, x_bar, x_bar_new, delta_bar, pressure, \ temp, file_fancyResults, doprint)
def lambdacorr(it_num, verb, input, info, save_info=None): ''' This module applies lambda correction method (Section 4 in the TEA theory document). When input mole numbers are negative, the code corrects them to positive values and pass them to the next iteration cycle. The code reads the values from the last lagrange output, the information from the header file, performs checks, and starts setting basic equations. It defines a 'smart' range so it can efficiently explore the lambda values from [0,1]. Half of the range is sampled exponentially, and the other half linearly, totalling 150 points. The code retrieves the last lambda value before first derivative becomes positive (equation (34) in TEA theory document), and corrects negative mole numbers to positive. Parameters ---------- it_num: integer Iteration number. verb: Integer Verbosity level (0=mute, 1=quiet, 2=verbose). input: List A list containing the results/data from the previous calculation in lagrange.py or lambdacorr.py: the current header directory, current iteration number, array of species names, array of initial guess, array of non-corrected Lagrange values, and array of lambdacorr corrected values. info: list pressure: atmospheric layer's pressure (bar) i: Number of species j: Number of elements a: Stoichiometric coefficients b: Elemental mixing fractions g_RT: Species chemical potential save_info: List of stuff If not None, save info to files. The list contains: [location_out, desc, speclist, temp] Returns ------- y: array of floats The initial guess of molecular species. x_corr: array of floats Final mole numbers of molecular species. delta_corr: array of floats Array containing change of initial and final mole numbers of molecular species for current iteration. y_bar: float Total initial guess of all molecular species for current iteration. x_corr_bar: float Total sum of the final mole numbers of all molecular species. detla_corr_bar: float Change in total number of all species. verb: Integer Verbosity level (0=mute, 1=quiet, 2=verbose). Notes ----- The code works without adjustments for the temperatures above ~500 K. For temperatures below 500 K the code produces results with low precision, thus it is not recommended to use TEA below 500 K. Setting xtol to 1e-8 and maxinter to 200 is most optimizing. If higher tolerance level is desired (xtol>1e-8), maxium number of iterations must be increased. The result can be further improved with fine adjustments to the lambda exploration variables 'lower' and 'steps' to larger magnitudes (i.e., lower = -100, steps = 1000). This will lengthen the time of execution. ''' # Suppress nan warnings, as they are used for finding valid minima np.seterr(invalid='ignore') np.seterr(divide='ignore') pressure = info[0] i = info[1] g_RT = info[5] # Take final values from last iteration, lagrange.py y = input[0] x = input[1] delta = input[2] y_bar = input[3] x_bar = input[4] delta_bar = input[5] # Create 'c' value, equation (17) TEA theory document # c_i = (g/RT)i + ln(P) c = g_RT + np.log(pressure) # Create the range of lambda values to explore. To speed up finding # the correct lambda value to use, the range is split into two # parts at range_split: the lower exponential range and the # higher linear range. This helps the system to converge faster. range_split = 0.5 # Create exponential range, low_range # Exponent parameter that gives a value close to zero for the start of # lambda exploration lower = -50 # Define number of steps to explore exponential range steps = 100 # Create lower exponential range low_range = np.exp(np.linspace(lower, 0, steps+1)) # Create linear, evenly spaced range, high_range high_step = 0.01 high_range = np.arange(0.5, 1 + high_step, high_step) # Combine the two ranges to create one overall range for lambda exploration smart_range = np.append(low_range[low_range <= range_split], high_range) # Set that lambda is not found lam_not_found = True # Retrieve last lambda value explored before the minimum energy is passed for h in smart_range: val = dF_dlam(h, i, x, y, delta, c, x_bar, y_bar, delta_bar) if val > 0 or np.isnan(val): break # If lambda found, take lambda and set lambda not found to false lam = h lam_not_found = False # If lambda is not found (break), function F (equation (33) in the TEA # theory document) set the final x mole numbers to the values calculated # in the last iteration output if lam_not_found: x_corr = y else: x_corr = y + lam * delta # Correct x values given this value of lambda x_corr_bar = np.sum(x_corr) delta_corr = y - x_corr delta_corr_bar = x_corr_bar - y_bar if save_info is not None: location_out, desc, speclist, temp = save_info hfolder = location_out + desc + "/headers/" headerfile = "{:s}/header_{:s}_{:.0f}K_{:.2e}bar.txt".format( hfolder, desc, temp, pressure) # Create and name outputs and results directories if they do not exist datadir = location_out + desc + '/outputs/' datadir = "{:s}/{:s}_{:.0f}K_{:.2e}bar/".format( datadir, desc, temp, pressure) # Export all values into machine and human readable output files file = '{:s}/lagrange_iteration-{:03d}_machine-read.txt'.format( datadir, it_num) form.output(headerfile, it_num, speclist, y, x_corr, delta_corr, y_bar, x_corr_bar, delta_corr_bar, file, verb) file = '{:s}/lagrange_iteration-{:03d}_visual.txt'.format( datadir, it_num) form.fancyout(it_num, speclist, y, x_corr, delta_corr, y_bar, x_corr_bar, delta_corr_bar, file, verb) return y, x_corr, delta_corr, y_bar, x_corr_bar, delta_corr_bar
if bool == False: print('Equation ' + np.str(m+1) + \ ' is NOT satisfied. Check for errors!') # Set iteration number to zero it_num = 0 # Put all initial mole numbers in y array y = y_init # Make y_bar (sum of all y values) y_bar = np.sum(y) # Initialize delta variables to 0. (this signifies the first iteration) delta = np.zeros(i) delta_bar = np.sum(delta) # Name output files with corresponding iteration number name file = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-machine-read.txt' file_fancy = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-visual.txt' # Put results into machine readable file form.output(datadir, header, it_num, speclist, y, y, delta, \ y_bar, y_bar, delta_bar, file, doprint) # Put results into human readable file form.fancyout(datadir, it_num, speclist, y, y, delta, y_bar, \ y_bar, delta_bar, file_fancy, doprint)
if doprint: printout('Max interation not met. Starting next iteration...\n') # ============== Stop the loop, max iteration reached ============== # # Stop if max iteration is reached else: # Print to screen printout('Maximum iteration reached, ending minimization.\n') # Set repeat to False to stop the loop repeat = False # Calculate delta values delta = x_new - x # Calculate delta_bar values delta_bar = x_bar_new - x_bar # Name output files with corresponding iteration number name file_results = datadirr + '/results-machine-read.txt' file_fancyResults = datadirr + '/results-visual.txt' # Export all values into machine and human readable output files form.output(datadirr, header, it_num, speclist, x, x_new, delta, \ x_bar, x_bar_new, delta_bar, file_results, doprint) form.fancyout_results(datadirr, header, it_num, speclist, x, x_new, \ delta, x_bar, x_bar_new, delta_bar, pressure, \ temp, file_fancyResults, doprint)
def lambdacorr(it_num, datadir, doprint, direct): ''' This module applies lambda correction method (see Section 4 in the TEA theory document). When input mole numbers are negative, the code corrects them to positive values and pass them to the next iteration cycle. The code reads the values from the last lagrange output, the information from the header file, performs checks, and starts setting basic equations. It defines a 'smart' range so it can efficiently explore the lambda values from [0,1]. Half of the range is sampled exponentially, and the other half linearly, totalling 150 points. The code retrieves the last lambda value before first derivative becomes positive (equation (34) in TEA theory document), and corrects negative mole numbers to positive. Parameters ---------- it_num: integer Iteration number. datadir: string Current directory where TEA is run. doprint: string Parameter in configuration file that allows printing for debugging purposes. direct: object Object containing all of the results/data from the previous calculation in lagrange.py or lambdacorr.py. It is a list containing current header directory, current iteration number, array of species names, array of initial guess, array of non-corrected Lagrange values, and array of lambdacorr corrected values. Returns ------- header: string Name of the header file used. it_num: integer Iteration number. speclist: array of strings Array containing names of molecular species. y: array of floats Array containing initial guess of molecular species for current iteration. x_corr: array of floats Array containing final mole numbers of molecular species for current iteration. delta_corr: array of floats Array containing change of initial and final mole numbers of molecular species for current iteration. y_bar: float Array containing total initial guess of all molecular species for current iteration. x_corr_bar: float Total sum of the final mole numbers of all molecular species. detla_corr_bar: float Change in total number of all species. doprint: string Parameter in configuration file that allows printing for debugging purposes. Notes ----- The code works without adjustments and with high precision for the temperatures above ~600 K. For temperatures below 600 K and mixing fractions below 10e-14, the code produces results with low precision. To improve the precision, adjust the lambda exploration variables 'lower' and 'steps' to larger magnitudes (i.e., lower = -100, steps = 1000). This will lengthen the time of execution. ''' # Suppress nan warnings, as they are used for finding valid minima np.seterr(invalid='ignore') np.seterr(divide='ignore') # Read values from last lagrange output input = direct # Take the current header file header = input[0] # Read values from the header file pressure, temp, i, j, speclist, a, b, g_RT = form.readheader(header) # Take final values from last iteration, lagrange.py y = input[3] x = input[4] delta = input[5] y_bar = input[6] x_bar = input[7] delta_bar = input[8] # Perform checks to be safe it_num_check = input[1] speclist_check = input[2] # Make array of checks check = np.array( [it_num_check != it_num, False in (speclist_check == speclist)]) # If iteration number given by iterate.py is not one larger as in 'direct', # give error if check[0]: print("\n\nMAJOR ERROR! Read in file's it_num is not the \ current iteration!\n\n") # If species names in the header are not the same as in 'direct', # give error if check[1]: print("\n\nMAJOR ERROR! Read in file uses different species \ order/list!\n\n") # Create 'c' value, equation (17) TEA theory document # c_i = (g/RT)i + ln(P) c = g_RT + np.log(pressure) # Set equation (34) TEA theory document # dF(lam)/dlam = sum_i delta_i[(g(T)/RT)_i + lnP + # ln (yi+lam*delta_i)/(y_bar+lam*delta_bar)] def dF_dlam(s, i, x, y, delta, c, x_bar, y_bar, delta_bar): dF_dlam = 0 for n in np.arange(i): dF_dlam += delta[n] * (c[n] + np.log(y[n] + s*delta[n]) - \ np.log(y_bar + s*delta_bar)) return dF_dlam # Create the range of lambda values to explore. To speed up finding # the correct lambda value to use, the range is split into two # parts at range_split: the lower exponential range and the # higher linear range. This helps the system to converge faster. range_split = 0.5 # Create exponential range, low_range # Exponent parameter that gives a value close to zero for the start of # lambda exploration lower = -50 # Define number of steps to explore exponential range steps = 100 # Create lower exponential range low_range = np.exp(np.linspace(lower, 0, steps + 1)) # Create linear, evenly spaced range, high_range high_step = 0.01 high_range = np.arange(0.5, 1 + high_step, high_step) # Combine the two ranges to create one overall range for lambda exploration smart_range = np.append(low_range[low_range <= range_split], high_range) # Set that lambda is not found lam_not_found = True # Retrieve last lambda value explored before the minimum energy is passed for h in smart_range: val = dF_dlam(h, i, x, y, delta, c, x_bar, y_bar, delta_bar) if val > 0 or np.isnan(val) == True: break # If lambda found, take lambda and set lambda not found to false lam = h lam_not_found = False # If lambda is not found (break), function F (equation (33) in the TEA # theory document) set the final x mole numbers to the values calculated # in the last iteration output if lam_not_found: x_corr = y else: x_corr = y + lam * delta # Correct x values given this value of lambda x_corr_bar = np.sum(x_corr) delta_corr = y - x_corr delta_corr_bar = x_corr_bar - y_bar # Name output files with corresponding iteration number name file = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-machine-read.txt' file_fancy = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-visual.txt' # Export all values into machine and human readable output files form.output(datadir, header, it_num, speclist, y, x_corr, delta_corr, \ y_bar, x_corr_bar, delta_corr_bar, file, doprint) form.fancyout(datadir, it_num, speclist, y, x_corr, delta_corr, y_bar, \ x_corr_bar, delta_corr_bar, file_fancy, doprint) return [header, it_num, speclist, y, x_corr, delta_corr, y_bar, \ x_corr_bar, delta_corr_bar, doprint]
def lambdacorr(it_num, datadir, doprint, direct): ''' This module applies lambda correction method (see Section 1.3 in TEA Document). When input mole numbers are negative, the code corrects them to positive values and pass them to the next iteration cycle. The code reads the values from the last lagrange output, the information from the header file, performs checks, and starts setting basic equations. It sets a smart range so it can efficiently explore the lambda values from [0,1]. Half of the range is sampled exponentially, and the other half linearly, totalling 150 points. The code retrieves the last lambda value before first derivative becomes positive (equation (33) in TEA Document), and corrects negative mole numbers to positive. Parameters ---------- it_num: integer Iteration number. datadir: string Current directory where TEA is run. doprint: string Parameter in configuration file that allows printing for debugging purposes. direct: object Object containing all of the results/data from the previous calculation in lagrange.py or lambdacorr.py. It is a list containing current header directory, current iteration number, array of species names, array of initial guess, array of non-corrected Lagrange values, and array of lambdacorr corrected values. Returns ------- header: string Name of the header file used. it_num: integer Iteration number. speclist: array of strings Array containing names of molecular species. y: array of floats Array containing initial guess of molecular species for current iteration. x_corr: array of floats Array containing final mole numbers of molecular species for current iteration. delta_corr: array of floats Array containing change of initial and final mole numbers of molecular species for current iteration. y_bar: float Array containing total initial guess of all molecular species for current iteration. x_corr_bar: float Total sum of the final mole numbers of all molecular species. detla_corr_bar: float Change in total number of all species. doprint: string Parameter in configuration file that allows printing for debugging purposes. Notes ----- The code works without adjustments and with high precision for the the fractional abundances (mixing fractions) up to 10e-14 and the temperature range of 1000 - 4000 K. For temperatures below 1000 K and mixing fractions below 10e-14, the code produces results with low precision. To improve the precision, adjust the lambda exploration variables 'lower' and 'steps' to larger magnitudes (i.e., lower = -100, steps = 1000). This will lengthen the time of execution. ''' # Suppress nan warnings, as they are used for finding valid minima np.seterr(invalid='ignore') np.seterr(divide='ignore') # Read values from last lagrange output input = direct # Take the current header file header = input[0] # Read values from the header file pressure, temp, i, j, speclist, a, b, g_RT = form.readheader(header) # Take final values from last iteration, lagrange.py y = input[3] x = input[4] delta = input[5] y_bar = input[6] x_bar = input[7] delta_bar = input[8] # Perform checks to be safe it_num_check = input[1] speclist_check = input[2] # Make array of checks check = np.array([it_num_check != it_num, False in (speclist_check == speclist) ]) # If iteration number given by iterate.py is not one larger as in 'direct', # give error if check[0]: print("\n\nMAJOR ERROR! Read in file's it_num is not the \ current iteration!\n\n") # If species names in the header are not the same as in 'direct', # give error if check[1]: print("\n\nMAJOR ERROR! Read in file uses different species \ order/list!\n\n") # Create 'c' value, equation (16) TEA Document # c_i = (g/RT)i + ln(P) c = g_RT + np.log(pressure) # Set equation (33) TEA Document # dF(lam)/dlam = sum_i delta_i[(g(T)/RT)_i + lnP + # ln (yi+lam*delta_i)/(y_bar+lam*delta_bar)] def dF_dlam(s, i, x, y, delta, c, x_bar, y_bar, delta_bar): dF_dlam = 0 for n in np.arange(i): dF_dlam += delta[n] * (c[n] + np.log(y[n] + s*delta[n]) - \ np.log(y_bar + s*delta_bar)) return dF_dlam # Create the range of lambda values to explore. To speed up finding # the correct lambda value to use, the range is split into two # parts at range_split: the lower exponential range and the # higher linear range. This helps the system to converge faster. range_split = 0.5 # Create exponential range, low_range # Exponent parameter that gives a value close to zero for the start of # lambda exploration lower = -50 # Define number of steps to explore exponential range steps = 100 # Create lower exponential range low_range = np.exp(np.linspace(lower, 0, steps+1)) # Create linear, evenly spaced range, high_range high_step = 0.01 high_range = np.arange(0.5, 1 + high_step, high_step) # Combine the two ranges to create one overall range for lambda exploration smart_range = np.append(low_range[low_range <= range_split], high_range) # Set that lambda is not found lam_not_found = True # Retrieve last lambda value explored before the minimum energy is passed for h in smart_range: val = dF_dlam(h, i, x, y, delta, c, x_bar, y_bar, delta_bar) if val > 0 or np.isnan(val) == True: break # If lambda found, take lambda and set lambda not found to false lam = h lam_not_found = False # If lambda is not found (break), function F (equation (35) in TEA # Document) set the final x mole numbers to the values calculated # in the last iteration output if lam_not_found: x_corr = y else: x_corr = y + lam * delta # Correct x values given this value of lambda x_corr_bar = np.sum(x_corr) delta_corr = y - x_corr delta_corr_bar = x_corr_bar - y_bar # Name output files with corresponding iteration number name file = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-machine-read.txt' file_fancy = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-visual.txt' # Export all values into machine and human readable output files form.output(datadir, header, it_num, speclist, y, x_corr, delta_corr, \ y_bar, x_corr_bar, delta_corr_bar, file, doprint) form.fancyout(datadir, it_num, speclist, y, x_corr, delta_corr, y_bar, \ x_corr_bar, delta_corr_bar, file_fancy, doprint) return [header, it_num, speclist, y, x_corr, delta_corr, y_bar, \ x_corr_bar, delta_corr_bar, doprint]
def lagrange(it_num, datadir, doprint, direct): ''' This code applies Lagrange's method and calculates minimum based on the methodology elaborated in the TEA theory document in Section (3). Equations in this code contain both references and an explicitly written definitions. The program reads the last iteration's output and data from the last header file, creates variables for the Lagrange equations, sets up the Lagrange equations, and calculates final x_i mole numbers for the current iteration cycle. Note that the mole numbers that result from this function are allowed to be negative. If negatives are returned, lambda correction (lambdacorr.py) is necessary. The final x_i values, as well as x_bar, y_bar, delta, and delta_bar are written into machine- and human-readable output files. This function is executed by iterate.py. Parameters ---------- it_num: integer Iteration number. datadir: string Current directory where TEA is run. doprint: string Parameter in configuration file that allows printing for debugging purposes. direct: object Object containing all of the results/data from the previous calculation in lagrange.py or lambdacorr.py. It is a list containing current header directory, current iteration number, array of species names, array of initial guess, array of non-corrected Lagrange values, and array of lambdacorr corrected values. Returns ------- header: string Name of the header file used. it_num: integer Iteration number. speclist: string array Array containing names of molecular species. y: float array Array containing initial guess of molecular species for current iteration. x: float array Array containing final mole numbers of molecular species for current iteration. delta: float array Array containing change in initial and final mole numbers of molecular species for current iteration. y_bar: float Array containing total sum of initial guesses of all molecular species for current iteration. x_bar: float Total sum of the final mole numbers of all molecular species. delta_bar: float Change in total of initial and final mole numbers of molecular species. ''' # Read values from last iteration input = direct # Take the current header file header = input[0] # Read values from the header file pressure, temp, i, j, speclist, a, b, g_RT = form.readheader(header) # Use final values from last iteration (x values) as new initial y = input[4] y_bar = input[7] # Perform checks to be safe it_num_check = input[1] speclist_check = input[2] # Make array of checks check = np.array([it_num_check != it_num - 1, False in (speclist_check == speclist) ]) # If iteration number given by iterate.py is not one larger as in 'direct', # give error if check[0]: print("\n\nMAJOR ERROR! Read in file's it_num is not the most \ recent iteration!\n\n") # If species names in the header are not the same as in 'direct', # give error if check[1]: print("\n\nMAJOR ERROR! Read in file uses different species \ order/list!\n\n") # ============== CREATE VARIABLES FOR LAGRANGE EQUATION ============== # # Create 'c' value, equation (18) TEA theory document # ci = (g/RT)_i + ln(P) c = g_RT + np.log(pressure) # Allocates array of fi(Y) over different values of i (species) fi_y = np.zeros(i) # Fill in fi(Y) values equation (19) TEA theory document # fi = x_i * [ci + ln(x_i/x_bar)] for n in np.arange(i): y_frac = np.float(y[n] / y_bar) fi_y[n] = y[n] * ( c[n] + np.log(y_frac) ) # Allocate values of rjk. Both j and k goes from 1 to m. k = j rjk = np.zeros((j,k)) # Fill out values of rjk, equation (26) TEA theory document # rjk = rkj = sum_i(a_ij * a_ik) * y_i for l in np.arange(k): for m in np.arange(j): r_sum = 0.0 for n in np.arange(i): r_sum += a[n, m] * a[n, l] * y[n] rjk[m, l] = r_sum # Allocate value of u, equation (28) TEA theory document u = Symbol('u') # Allocate pi_j variables, where j is element index # Example: pi_2 is Lagrange multiplier of N (j = 2) pi = [] for m in np.arange(j): name = 'pi_' + np.str(m+1) pi = np.append(pi, Symbol(name)) # Allocate rjk * pi_j summations, equation (27) TEA theory document # There will be j * k terms of rjk * pi_j sq_pi = [pi] for m in np.arange(j-1): # Make square array of pi values with shape j * k sq_pi = np.append(sq_pi, [pi], axis = 0) # Multiply rjk * sq_pi to get array of rjk * pi_j # equation (27) TEA theory document rpi = rjk * sq_pi # ======================= SET FINAL EQUATIONS ======================= # # Total number of equations is j + 1 # Set up a_ij * fi(Y) summations equation (27) TEA theory document # sum_i[a_ij * fi(Y)] aij_fiy = np.zeros((j)) for m in np.arange(j): rhs = 0.0 for n in np.arange(i): rhs += a[n,m] * fi_y[n] aij_fiy[m] = rhs # Create first j'th equations equation (27) TEA theory document # r_1m*pi_1 + r_2m*pi_2 + ... + r_mm*pi_m + b_m*u = sum_i[a_im * fi(Y)] for m in np.arange(j): if m == 0: equations = np.array([np.sum(rpi[m]) + b[m]*u - aij_fiy[m]]) else: lagrange_eq = np.array([np.sum(rpi[m]) + b[m]*u - aij_fiy[m]]) equations = np.append(equations, lagrange_eq) # Last (j+1)th equation (27) TEA theory document # b_1*pi_1 + b_2*pi_2 + ... + b_m*pi_m + 0*u = sum_i[fi(Y)] bpi = b * pi lagrange_eq_last = np.array([np.sum(bpi) - np.sum(fi_y)]) equations = np.append(equations, lagrange_eq_last) # List all unknowns in the above set of equations unknowns = list(pi) unknowns.append(u) # Solve final system of j+1 equations fsol = solve(list(equations), unknowns, rational=False) # ============ CALCULATE xi VALUES FOR CURRENT ITERATION ============ # # Make array of pi values pi_f = [] for m in np.arange(j): pi_f = np.append(pi_f, [fsol[pi[m]]]) # Calculate x_bar from solution to get 'u', equation (28) TEA theory document # u = -1. + (x_bar/y_bar) u_f = fsol[u] x_bar = (u_f + 1.) * y_bar # Initiate array for x values of size i x = np.zeros(i) # Apply Lagrange solution for final set of x_i values for this iteration # equation (23) TEA theory document # x_i = -fi(Y) + (y_i/y_bar) * x_bar + [sum_j(pi_j * a_ij)] * y_i for n in np.arange(i): sum_pi_aij = 0.0 for m in np.arange(j): sum_pi_aij += pi_f[m] * a[n, m] x[n] = - fi_y[n] + (y[n]/y_bar) * x_bar + sum_pi_aij * y[n] # Calculate other variables of interest x_bar = np.sum(x) # sum of all x_i delta = x - y # difference between initial and final values delta_bar = x_bar - y_bar # difference between sum of initial and # final values # Name output files with corresponding iteration number name file = datadir + '/lagrange-iteration-' + np.str(it_num) + \ 'machine-read-nocorr.txt' file_fancy = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-visual-nocorr.txt' # Export all values into machine and human readable output files form.output(datadir, header, it_num, speclist, y, x, \ delta, y_bar, x_bar, delta_bar, file, doprint) form.fancyout(datadir, it_num, speclist, y, x, delta,\ y_bar, x_bar, delta_bar, file_fancy, doprint) return [header, it_num, speclist, y, x, delta, y_bar, x_bar, delta_bar]
def lagrange(it_num, verb, input, info, save_info=None): ''' This code applies Lagrange's method and calculates minimum based on the methodology elaborated in the TEA theory document in Section (3). Equations in this code contain both references and an explicitly written definitions. The program reads the last iteration's output and data from the last header file, creates variables for the Lagrange equations, sets up the Lagrange equations, and calculates final x_i mole numbers for the current iteration cycle. Note that the mole numbers that result from this function are allowed to be negative. If negatives are returned, lambda correction (lambdacorr.py) is necessary. The final x_i values, as well as x_bar, y_bar, delta, and delta_bar are written into machine- and human-readable output files. This function is executed by iterate.py. Parameters ---------- it_num: integer Iteration number. verb: Integer Verbosity level (0=mute, 1=quiet, 2=verbose). input: List The input values/data from the previous calculation in lagrange.py or lambdacorr.py. It is a list containing current header directory, current iteration number, array of species names, array of initial guess, array of non-corrected Lagrange values, and array of lambdacorr corrected values. info: List pressure: atmospheric layer's pressure (bar) i: Number of species j: Number of elements a: Stoichiometric coefficients b: Elemental mixing fractions g_RT: Species chemical potential save_info: string Current directory where TEA is run. Returns ------- y: float array Input guess of molecular species. x: float array Final mole numbers of molecular species. delta: float array Array containing change in initial and final mole numbers of molecular species for current iteration. y_bar: float Array containing total sum of initial guesses of all molecular species for current iteration. x_bar: float Total sum of the final mole numbers of all molecular species. delta_bar: float Change in total of initial and final mole numbers of molecular species. ''' # Read values from the header file pressure = info[0] j = info[2] a = info[3] b = info[4] g_RT = info[5] # Use final values from last iteration (x values) as new initial y = input[1] y_bar = input[4] # ============== CREATE VARIABLES FOR LAGRANGE EQUATION ============== # # Create 'c' value, equation (18) TEA theory document # ci = (g/RT)_i + ln(P) c = g_RT + np.log(pressure) # Fill in fi(Y) values equation (19) TEA theory document # fi = x_i * [ci + ln(x_i/x_bar)] fi_y = y * (c + np.log(y / y_bar)) # Allocate value of u, equation (28) TEA theory document # List all unknowns in the above set of equations unknowns = [None] * (j + 1) # Allocate pi_j, where j is element index for m in np.arange(j): unknowns[m] = Symbol('pi_' + str(m + 1)) unknowns[j] = Symbol('u') # ================= SET SYSTEM OF EQUATIONS ======================= # # Total number of equations is j + 1 # Create first j'th equations equation (27) TEA theory document # r_1j*pi_1 + r_2j*pi_2 + ... + r_jj*pi_j + b_j*u = sum_i[a_ij * fi(Y)] system = np.zeros((j + 1, j + 2)) # Fill out values of r_ij, equation (26) TEA theory document # rjk = rkj = sum_i(a_ij * a_ik) * y_i for l in np.arange(j): for m in np.arange(j): system[m, l] = np.sum(a[:, m] * a[:, l] * y) # Last column, b_j*u: system[:j, j] = b # Set up a_ij * fi(Y) summations equation (27) TEA theory document # sum_i[a_ij * fi(Y)] system[:j, j + 1] = np.sum(a.T * fi_y, axis=1) # Last (j+1)th equation (27) TEA theory document # b_1*pi_1 + b_2*pi_2 + ... + b_m*pi_m = sum_i[fi(Y)] system[j, :j] = b system[j, j + 1] = np.sum(fi_y) # Solve final system of j+1 equations fsol = solve_linear_system(Matrix(system), *unknowns) # Make array of pi values pi_f = np.zeros(j, np.double) for m in np.arange(j): pi_f[m] = fsol[unknowns[m]] fsolu = fsol[unknowns[j]] # ============ CALCULATE xi VALUES FOR CURRENT ITERATION ============ # # Calculate x_bar from solution to get 'u', eq (28) TEA theory document # u = -1. + (x_bar/y_bar), where fsolu is u x_bar = (fsolu + 1.0) * y_bar # Apply Lagrange solution for final set of x_i values for this iteration # equation (23) TEA theory document # x_i = -fi(Y) + (y_i/y_bar) * x_bar + [sum_j(pi_j * a_ij)] * y_i sum_pi_aij = np.sum(pi_f * a, axis=1) x = np.array(-fi_y + (y / y_bar) * x_bar + sum_pi_aij * y, np.double) # Calculate other variables of interest x_bar = np.sum(x) # sum of all x_i delta = x - y # difference between initial and final values delta_bar = x_bar - y_bar # difference between sum of initial and # final values # Name output files with corresponding iteration number name if save_info: location_out, desc, speclist, temp = save_info hfolder = location_out + desc + "/headers/" headerfile = "{:s}/header_{:s}_{:.0f}K_{:.2e}bar.txt".format( hfolder, desc, temp, pressure) # Create and name outputs and results directories if they do not exist datadir = location_out + desc + '/outputs/' datadir = "{:s}/{:s}_{:.0f}K_{:.2e}bar/".format( datadir, desc, temp, pressure) if not os.path.exists(datadir): os.makedirs(datadir) # Export all values into machine and human readable output files file = '{:s}/lagrange_iteration-{:03d}_machine-read-nocorr.txt'.format( datadir, it_num) form.output(headerfile, it_num, speclist, y, x, delta, y_bar, x_bar, delta_bar, file, verb) file = '{:s}/lagrange_iteration-{:03d}_visual-nocorr.txt'.format( datadir, it_num) form.fancyout(it_num, speclist, y, x, delta, y_bar, x_bar, delta_bar, file, verb) return y, x, delta, y_bar, x_bar, delta_bar
def iterator(head, destination, location_out): # Correct location_TEA name if location_out[-1] != '/': location_out += '/' # Time / speed testing if times: end = time.time() elapsed = end - start print("iterate.py imports: " + str(elapsed)) # Read run-time arguments header = head # Name of header file desc = destination # Directory name # Create and name outputs and results directories if they do not exist datadir = location_out + desc + '/outputs/' + 'transient/' datadirr = location_out + desc + '/results' if not os.path.exists(datadir): os.makedirs(datadir) if not os.path.exists(datadirr): os.makedirs(datadirr) # Retrieve header info inhead = form.readheader(header) pressure = inhead[0] temp = inhead[1] # Locate and read initial iteration output from balance.py infile = datadir + '/lagrange-iteration-0-machine-read.txt' input = form.readoutput(infile) # Retrieve and set initial values speclist = input[2] x = input[3] x_bar = input[6] # Set up first iteration it_num = 1 repeat = True # Prepare data object for iterative process # (see description of the 'direct' object in lagrange.py) lambdacorr_data = [header, 0, speclist, x, x, 0, x_bar, x_bar, 0] # Time / speed testing if times: new = time.time() elapsed = new - end print("pre-loop setup: " + str(elapsed)) # ====================== PERFORM MAIN TEA LOOP ====================== # while repeat: # Output iteration number if ((not doprint) & (not times)): stdout.write(' ' + str(it_num) + '\r') stdout.flush() # Time / speed testing for lagrange.py if times: ini = time.time() # Execute Lagrange minimization lagrange_data = lg.lagrange(it_num, datadir, doprint, lambdacorr_data) # Time / speed testing for lagrange.py if times: fin = time.time() elapsed = fin - ini print("lagrange" + str(it_num).rjust(4) + " : " + str(elapsed)) # Print for debugging purposes if doprint: printout('Iteration %d Lagrange complete. Starting lambda correction...', it_num) # Take final x_i mole numbers from last Lagrange calculation lagrange_x = lagrange_data[4] # Check if x_i have negative mole numbers, and if yes perform lambda correction if where((lagrange_x < 0) == True)[0].size != 0: # Print for debugging purposes if doprint: printout('Correction required. Initializing...') # Time / speed testing for lambdacorr.py if times: ini = time.time() # Execute lambda correction lambdacorr_data = lc.lambdacorr(it_num, datadir, doprint, \ lagrange_data) # Print for debugging purposes if times: fin = time.time() elapsed = fin - ini print("lambcorr" + str(it_num).rjust(4) + " : " + \ str(elapsed)) # Print for debugging purposes if doprint: printout('Iteration %d lambda correction complete. Checking precision...', it_num) # Lambda correction is not needed else: # Pass previous Lagrange results as inputs to next iteration lambdacorr_data = lagrange_data # Print for debugging purposes if doprint: printout('Iteration %d did not need lambda correction.', it_num) # Retrieve most recent iteration values input_new = lambdacorr_data # Take most recent x_i and x_bar values x_new = input_new[4] x_bar_new = input_new[7] # If max iteration not met, continue with next iteration cycle if it_num < maxiter: # Add 1 to iteration number it_num += 1 # Print for debugging purposes if doprint: printout('Max interation not met. Starting next iteration...\n') # ============== Stop the loop, max iteration reached ============== # # Stop if max iteration is reached else: # Print to screen printout('Maximum iteration reached, ending minimization.\n') # Set repeat to False to stop the loop repeat = False # Calculate delta values delta = x_new - x # Calculate delta_bar values delta_bar = x_bar_new - x_bar # Name output files with corresponding iteration number name file_results = datadirr + '/results-machine-read.txt' file_fancyResults = datadirr + '/results-visual.txt' # Export all values into machine and human readable output files form.output(datadirr, header, it_num, speclist, x, x_new, delta, \ x_bar, x_bar_new, delta_bar, file_results, doprint) form.fancyout_results(datadirr, header, it_num, speclist, x, x_new, \ delta, x_bar, x_bar_new, delta_bar, pressure, \ temp, file_fancyResults, doprint)
print('Equation ' + np.str(m+1) + \ ' is NOT satisfied. Check for errors!') # Set iteration number to zero it_num = 0 # Put all initial mole numbers in y array y = y_init # Make y_bar (sum of all y values) y_bar = np.sum(y) # Initialize delta variables to 0. (this signifies the first iteration) delta = np.zeros(i) delta_bar = np.sum(delta) # Name output files with corresponding iteration number name file = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-machine-read.txt' file_fancy = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-visual.txt' # Put results into machine readable file form.output(datadir, header, it_num, speclist, y, y, delta, \ y_bar, y_bar, delta_bar, file, doprint) # Put results into human readable file form.fancyout(datadir, it_num, speclist, y, y, delta, y_bar, \ y_bar, delta_bar, file_fancy, doprint)
def iterate(pressure, a, b, g_RT, maxiter, verb, times, guess, xtol=1e-8, save_info=None): """ Run iterative Lagrangian minimization and lambda correction. Parameters ---------- pressure: Float Atmospheric pressure (bar). a: 2D float ndarray Species stoichiometric coefficients. b: 1D float ndarray Elemental mixing fractions. g_RT: 1D float ndarray Species chemical potentials. maxiter: Integer Maximum number of iterations. verb: Integer Verbosity level (0=mute, 1=quiet, 2=verbose). times: Bool If True, track excecution times. guess: 1D list A two-element list with input guess values for x and x_bar. xtol: Float Error between iterations that is acceptable for convergence. The routine has converged when the sum of the relative improvement per species becomes less than xtol, i.e.: sum(abs((y-x)/y)) / len(x) <= xtol. save_info: List of stuff If not None, save info to files. The list contains: [location_out, desc, speclist, temp] Returns ------- y: float array Input guess of molecular species. x: float array Final mole numbers of molecular species. delta: float array Array containing change in initial and final mole numbers of molecular species for current iteration. y_bar: float Array containing total sum of initial guesses of all molecular species for current iteration. x_bar: float Total sum of the final mole numbers of all molecular species. delta_bar: float Change in total of initial and final mole numbers of molecular species. """ # Retrieve header info i, j = np.shape(a) # Retrieve and set initial values x, x_bar = guess # Prepare data object for iterative process # (see description of the 'input' object in lagrange.py) lc_data = x, x, 0, x_bar, x_bar, 0 info = pressure, i, j, a, b, g_RT # ====================== PERFORM MAIN TEA LOOP ====================== # it_num = 1 while it_num <= maxiter: # Output iteration number if verb >= 1 and (it_num % 10) == 0: stdout.write(' {:d}\r'.format(it_num)) stdout.flush() # Time / speed testing for lagrange.py if times: ini = time.time() # Execute Lagrange minimization: lc_data = lg.lagrange(it_num, verb, lc_data, info, save_info) # Time / speed testing for lagrange.py if times: fin = time.time() elapsed = fin - ini print("lagrange {:>4d}: {:f} s.".format(it_num, elapsed)) # Print for debugging purposes if verb > 1: printout("Iteration {:d} Lagrange complete. Starting lambda " "correction...".format(it_num)) # Check if x_i have negative mole numbers, if so, do lambda correction if np.any(lc_data[1] < 0): # Print for debugging purposes if verb > 1: printout('Correction required. Initializing...') # Time / speed testing for lambdacorr.py if times: ini = time.time() # Execute lambda correction lc_data = lc.lambdacorr(it_num, verb, lc_data, info, save_info) # Print for debugging purposes if times: fin = time.time() elapsed = fin - ini print("lambcorr {:>4d}: {:f} s.".format(it_num, elapsed)) # Print for debugging purposes if verb > 1: printout("Iteration {:d} lambda correction complete. " "Checking precision...".format(it_num)) # Lambda correction is not needed else: # Print for debugging purposes if verb > 1: printout( 'Iteration {:d} did not need lambda correction.'.format( it_num)) # Check the tolerance xdiff = (lc_data[1] / lc_data[4]) / (lc_data[0] / lc_data[3]) - 1 if np.sum(np.abs(xdiff)) / len(xdiff) <= xtol: if verb >= 1: stdout.write(' {:d}\r'.format(it_num)) printout( "The solution has converged to the given tolerance error.\n" ) break it_num += 1 # If max iteration not met, continue with next iteration cycle if verb > 1: printout('Max interation not met. Starting next iteration...\n') # ============== Stop the loop, max iteration reached ============== # # Stop if max iteration is reached if verb >= 1 and it_num == maxiter + 1: printout('Maximum iteration reached, ending minimization.\n') # Retrieve most recent iteration values input_new = lc_data # Take most recent x_i and x_bar values x_new = input_new[1] x_bar_new = input_new[4] # Calculate delta values delta = x_new - x # Calculate delta_bar values delta_bar = x_bar_new - x_bar if save_info is not None: location_out, desc, speclist, temp = save_info hfolder = location_out + desc + "/headers/" headerfile = "{:s}/header_{:s}_{:.0f}K_{:.2e}bar.txt".format( hfolder, desc, temp, pressure) # Create and name outputs and results directories if they do not exist datadirr = '{:s}{:s}/results/results_{:.0f}K_{:.2e}bar'.format( location_out, desc, temp, pressure) if not os.path.exists(datadirr): os.makedirs(datadirr) # Export all values into machine and human readable output files file = "{:s}/results-machine-read.txt".format(datadirr) form.output(headerfile, it_num, speclist, x, x_new, delta, x_bar, x_bar_new, delta_bar, file, verb) file = "{:s}/results-visual.txt".format(datadirr) form.fancyout_results(headerfile, it_num, speclist, x, x_new, delta, x_bar, x_bar_new, delta_bar, pressure, temp, file, verb) return input_new
def balanceFunction(head, destination, location_out): if location_out[-1] != '/': location_out += '/' header = head # Name of header file desc = destination # Directory name # Create and name outputs and results directories if they do not exist datadir = location_out + desc + '/outputs/' + 'transient/' # If output directory does not exist already, make it if not os.path.exists(datadir): os.makedirs(datadir) # Read in values from header file pressure, temp, i, j, speclist, a, b, c = form.readheader(header) # Print b values for debugging purposes if doprint: print("b values: " + str(b)) # Find chunk of ai_j array that will allow the corresponding yi values # to be free variables such that all elements are considered for n in np.arange(i - j + 1): # Get lower and upper indices for chunk of ai_j array to check lower = n upper = n + j # Retrieve chunk of ai_j that would contain free variables a_chunk = a[lower:upper] # Sum columns to get total of ai_j in chunk for each species 'j' check = map(sum,zip(*a_chunk)) # Look for zeros in check. If a zero is found, this chunk of data can't # be used for free variables, as this signifies an element is ignored has_zero = 0 in check # If zero not found, create list of free variables' indices if has_zero == False: free_id = [] for m in np.arange(j): if doprint == True: print('Using y_' + np.str(n + m + 1) + ' as a free variable') free_id = np.append(free_id, n + m) break # Set initial guess of non-free y_i scale = 0.1 # Assume that all or some y_i are negative or zeros nofit = True # Loop until all y_i are non-zero positive while nofit: # Set up list of 'known' initial mole numbers before and after free chunk pre_free = np.zeros(free_id[0]) + scale post_free = np.zeros(i - free_id[-1] - 1) + scale # Set up list of free variables free = [] for m in np.arange(j): name = 'y_unknown_' + np.str(m) free = np.append(free, Symbol(name)) # Combine free and 'known' to make array of y_initial mole numbers y_init = np.append(pre_free, free) y_init = np.append( y_init, post_free) # Make 'j' equations satisfying mass balance equation (17) in TEA theory doc: # sum_i(ai_j * y_i) = b_j eq = [[]] for m in np.arange(j): rhs = 0 for n in np.arange(i): rhs += a[n, m] * y_init[n] rhs -= b[m] eq = np.append(eq, rhs) # Solve system of linear equations to get free y_i variables result = solve(list(eq), list(free), rational=False) # Correct for no-solution-found results. # If no solution found, decrease scale size. if result == []: scale /= 10 if doprint: print("Correcting initial guesses for realistic mass. \ Trying " + str(scale) + "...") # Correct for negative-mass results. If found, decrease scale size. else: # Assume no negatives and check hasneg = False for m in np.arange(j): if result[free[m]] < 0: hasneg = True # If negatives found, decrease scale size if hasneg: scale /= 10 if doprint: print("Negative numbers found in fit.") print("Correcting initial guesses for realistic mass. \ Trying " + str(scale) + "...") # If no negatives found, exit the loop (good fit is found) else: nofit = False if doprint: print(str(scale) + " provided a viable initial guess.") # Gather the results fit = [] for m in np.arange(j): fit = np.append(fit, result[free[m]]) # Put the result into the final y_init array y_init[free_id[0]:free_id[j-1]+1] = fit # This part of the code is only for debugging purposes # It rounds the values and checks whether the balance equation is satisfied # No values are changed and this serves solely as a check if doprint == True: print('\nCHECKS:') for m in np.arange(j): bool = round((sum(a[:,m] * y_init[:])), 2) == round(b[m], 2) if bool == True: if doprint == True: print('Equation ' + np.str(m+1) + ' is satisfied.') if bool == False: print('Equation ' + np.str(m+1) + \ ' is NOT satisfied. Check for errors!') # Set iteration number to zero it_num = 0 # Put all initial mole numbers in y array y = y_init # Make y_bar (sum of all y values) y_bar = np.sum(y) # Initialize delta variables to 0. (this signifies the first iteration) delta = np.zeros(i) delta_bar = np.sum(delta) # Name output files with corresponding iteration number name file = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-machine-read.txt' file_fancy = datadir + '/lagrange-iteration-' + np.str(it_num) + \ '-visual.txt' # Put results into machine readable file form.output(datadir, header, it_num, speclist, y, y, delta, \ y_bar, y_bar, delta_bar, file, doprint) # Put results into human readable file form.fancyout(datadir, it_num, speclist, y, y, delta, y_bar, \ y_bar, delta_bar, file_fancy, doprint)