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)
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
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, 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 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]
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 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)