def test_taylor_maccoll_given_theta(self): # Conical shock from cone with half-angle 20deg. s1 = Gas({'Air': 1.0}) s1.set_pT(100.0e3, 300.0) M1 = 1.5 V1 = M1 * s1.a beta = beta_cone(s1, V1, 20.0 * math.pi / 180) self.assertAlmostEqual(beta * 180 / math.pi, 49.0, delta=0.05) return
def test_shock_given_V_with_ideal_guess(self): s1 = Gas({'Air': 1.0}) s1.set_pT(10.0, 300.0) self.assertAlmostEqual(s1.rho, 1.1611e-4, delta=0.001) s2 = s1.clone() V2, Vg = normal_shock(s1, 14000.0, s2, {'gam': 1.35, 'R': 571.49}) self.assertAlmostEqual(s2.p, 20335.0, delta=1.0) self.assertAlmostEqual(s2.T, 32753.03, delta=1.0) self.assertAlmostEqual(V2, 1496.26, delta=1.0) self.assertAlmostEqual(Vg, 12503.74, delta=1.0)
def test_shock_given_V(self): s1 = Gas({'Air': 1.0}) s1.set_pT(1.0e5, 300.0) self.assertAlmostEqual(s1.rho, 1.1612, delta=0.001) s2 = s1.clone() V2, Vg = normal_shock(s1, 3000.0, s2) self.assertAlmostEqual(s2.p, 9.1779e+06, delta=9.1779e+06 * 1.0e-4) self.assertAlmostEqual(s2.T, 3572.97, delta=1.0) self.assertAlmostEqual(V2, 394.09, delta=1.0) self.assertAlmostEqual(Vg, 2605.9, delta=1.0) return
def test_reflected_shock(self): s1 = Gas({'Air': 1.0}) s1.set_pT(1.0e5, 300.0) s2 = s1.clone() V2, Vg = normal_shock(s1, 3000.0, s2) s5 = s1.clone() Vr_b = reflected_shock(s2, Vg, s5) self.assertAlmostEqual(s5.p, 8.4329e+07, delta=10.0e3) self.assertAlmostEqual(s5.T, 6121.63, delta=1.0) self.assertAlmostEqual(s5.rho, 43.909, delta=0.01) self.assertAlmostEqual(Vr_b, 656.69, delta=0.1) return
def test_oblique_shock(self): # essentially ideal gas at these conditions s1 = Gas({'Air': 1.0}) s1.set_pT(100.0e3, 300.0) M1 = 1.5 beta = 45.0 * math.pi / 180 V1 = M1 * s1.a theta, V2, s2 = theta_oblique(s1, V1, beta) self.assertAlmostEqual(s2.p, 114620, delta=50) self.assertAlmostEqual(theta, theta_obl(M1, beta), delta=0.001) beta2 = beta_oblique(s1, V1, theta) self.assertAlmostEqual(beta2 * 180 / math.pi, 45.0, delta=0.01) return
def test_finite_wave(self): V1 = 0.0 s9 = Gas({'Air': 1.0}) s9.set_pT(1.0e5, 320.0) # keep gas close to ideal Jplus = V1 + 2 * s9.a / (1.4 - 1) V2, s10 = finite_wave_dp('cplus', V1, s9, 60.0e3) ideal_V2 = Jplus - 2 * s10.a / (1.4 - 1) self.assertAlmostEqual(V2, ideal_V2, delta=2.0) V1 = 0.0 s9.set_pT(1.0e5, 320.0) Jplus = V1 + 2 * s9.a / (1.4 - 1) V2, s10 = finite_wave_dv('cplus', V1, s9, 125.0) self.assertAlmostEqual(Jplus, V2 + 2 * s10.a / (1.4 - 1), delta=2.0) return
def add_mass_fractions_to_block(blk): # Create cea2_gas object air = Gas({'Air': 1.0}, outputUnits='massf') for isp, s in enumerate(speciesList): key = "massf[%i]-%s" % (isp, s) blk.data[key] = zeros((blk.ni, blk.nj, blk.nk), 'd') blk.vars.append(key) for k in range(blk.nk): for j in range(blk.nj): for i in range(blk.ni): p = blk.data['p'][i, j, k] T = blk.data['T[0]'][i, j, k] air.set_pT(p, T) massf = air.get_fractions(speciesList) for isp, mf in enumerate(massf): key = "massf[%i]-%s" % (isp, speciesList[isp]) blk.data[key][i, j, k] = mf return
def test_expand_from_stagnation(self): s1 = Gas({'Air': 1.0}) s1.set_pT(1.0e5, 300.0) s2 = s1.clone() V2, Vg = normal_shock(s1, 3000.0, s2) s5 = s1.clone() Vr_b = reflected_shock(s2, Vg, s5) s6, V = expand_from_stagnation(0.0025, s5) self.assertAlmostEqual(s6.p, 210820.0, delta=10.0) self.assertAlmostEqual(s6.T, 2331.29, delta=1.0) self.assertAlmostEqual(s6.rho, 0.31475, delta=0.0001) self.assertAlmostEqual(V, 3766.2, delta=0.1) s7 = total_condition(s6, V) self.assertAlmostEqual(s7.p, 8.4013e+07, delta=10.0e3) self.assertAlmostEqual(s7.T, 6120.72, delta=1.0) self.assertAlmostEqual(s7.rho, 43.748, delta=0.01) s8 = pitot_condition(s6, V) self.assertAlmostEqual(s8.p, 4.3691e+06, delta=4.3691e+06 * 1.0e-4) self.assertAlmostEqual(s8.T, 5450.51, delta=1.0) self.assertAlmostEqual(s8.rho, 2.4222, delta=0.01) return
def test_taylor_maccoll_given_beta(self): # At these conditions, expect essentially-ideal gas flow. # M1=1.5, beta=49deg, expect theta=20deg from NACA1135. s1 = Gas({'Air': 1.0}) s1.set_pT(100.0e3, 300.0) M1 = 1.5 V1 = M1 * s1.a beta = 49.0 * math.pi / 180 theta_c, V_c, s_c = theta_cone(s1, V1, beta) self.assertAlmostEqual(theta_c * 180.0 / math.pi, 20.0, delta=0.05) surface_pressure_coeff = (s_c.p - s1.p) / (0.5 * s1.rho * V1 * V1) self.assertAlmostEqual(surface_pressure_coeff, 0.385, delta=0.01) # M1=1.8, beta=45deg, theta=24deg from NACA1135. M1 = 1.8 V1 = M1 * s1.a beta = 45.0 * math.pi / 180 theta_c, V_c, s_c = theta_cone(s1, V1, beta) self.assertAlmostEqual(theta_c * 180.0 / math.pi, 24.0, delta=0.1) surface_pressure_coeff = (s_c.p - s1.p) / (0.5 * s1.rho * V1 * V1) self.assertAlmostEqual(surface_pressure_coeff, 0.466, delta=0.01) return
def main(): # These are the inputs! primary_shock = 3376.0 # m/s secondary_shock = 6580.0 # m/s nozzle_exp_ratio = 7.5 conehead_pressure = 26642.0 # Pa # Set up the shock tube test gas print "Titan gas" state1 = Gas({ 'N2': 0.95, 'CH4': 0.05 }, inputUnits='moles', outputUnits='moles') state1.set_pT(25400.0, 300.0) print "state1:" state1.write_state(sys.stdout) # Set up the acceleration tube gas print "Air accelerator gas" state10 = Gas({'Air': 1.0}) state10.set_pT(68.7, 300.0) print "state10:" state10.write_state(sys.stdout) # Compute the primary shock here print "Incident shock" state2 = state1.clone() V2, V2g = normal_shock(state1, primary_shock, state2) print "V2=", V2, "Vg=", V2g print "state2:" state2.write_state(sys.stdout) # For the unsteady expansion of the test gas, regulation of the amount # of expansion is determined by the shock-processed accelerator gas. print "\nNow do unsteady expansion..." V5g, state5 = finite_wave_dv('cplus', V2g, state2, secondary_shock) # Write out expanded test gas conditions print "\nExpanded test gas, at end of acceleration tube:" print "V5g=", V5g print "state5:" state5.write_state(sys.stdout) # Now lets put in the nozzle with a steady flow area change V6g, state6 = steady_flow_with_area_change(state5, V5g, nozzle_exp_ratio) # Print out nozzle expanded flow properties print "\nNozzle exit gas conditions for an area ratio of:", nozzle_exp_ratio print "V6g:", V6g print "state6:" state6.write_state(sys.stdout) # Try and work out my conehead pressure for this particular flow condition # First I need to work out the shock angle shock_angle = beta_cone(state6, V6g, math.radians(15)) print "\nShock angle over cone:", math.degrees(shock_angle) # Reverse the process to get the flow state behind the shock and check the surface angle is correct delta_s, vel_cond, state7 = theta_cone(state6, V6g, shock_angle) print "Surface angle should be the same.....: 15deg = ", math.degrees( delta_s), "deg" print "\nConehead surface conditions:" state7.write_state(sys.stdout) # Need to check whether the pressure are the same print "\nComputed conehead pressure should be the same as the experimental pressure" print "Computed:", state7.p, "Experimental:", conehead_pressure print "If pressure are not the same 'tweak' the nozzle_exp_ratio until they match" # Grab the total conditions total6 = total_condition(state6, V6g) # Now print my total conditions print "\nTotal conditions of nozzle exit test gas:" print "total6:" total6.write_state(sys.stdout) # And finally.... let's output a 'flight equivalent' speed flight_eq = math.sqrt(2 * total6.h) print "\nFlight equivalent speed:", flight_eq print "Done." return
def calculate_pressure(data, variable_list, theta): """ Using the input data, determine the gas properties and then calculate the expected pressure on the surface of a cone of half-angle theta assuming an equilibrium gas. """ theta_rad = theta * numpy.pi / 180.0 # Find the species present in data set species_onlyList = [] for name in variable_list: if name.startswith('massf'): speciesName = name.split('-')[-1] species_onlyList.append(speciesName) # Initialise some variables eq_pitot_p = [] cone_beta = [] cone_theta = [] cone_V = [] cone_p = [] cone_T = [] for j in range(len(data['pos.y'])): # Create Gas object for current location... react = {} for spc in species_onlyList: react[spc] = data['massf['+\ str(species_onlyList.index(spc))+']-'+spc][j] #print "reactants=",react gasState = Gas(reactants=react, inputUnits='massf',\ onlyList=species_onlyList,\ outputUnits='massf') # Set pressure and temperature... gasState.set_pT(p=data['p'][j], T=data['T[0]'][j]) # Calculate the pitot pressure pitotState = pitot_condition(gasState, data['vel.x'][j]) eq_pitot_p.append(pitotState.p) #print eq_pitot_p # Calculate the cone static pressure # # Only if the Mach number is greater than 1 do we calculate # the surface pressure on the cone using the Taylor-Maccoll # equations. Otherwise we just set it equal to the freestream # pressure (we aren't interested in these low supersonic # regions). if data['M_local'][j] > 2.1: #1.05: # Calculate the conical shock angle cone_beta.append(beta_cone(gasState, data['vel.x'][j], theta_rad)) #print cone_beta # Calculate the gas properties on the cone surface theta_c, V_c, coneState = theta_cone(gasState, data['vel.x'][j], cone_beta[j]) cone_theta.append(theta_c) cone_V.append(V_c) cone_p.append(coneState.p) cone_T.append(coneState.T) #assert abs(theta_c-theta_rad) < 1.0e-4 print "theta_error=", abs(theta_c - theta_rad) else: cone_p.append(data['p'][j]) cone_T.append(data['T[0]'][j]) cone_V.append(0.0) cone_theta.append(0.0) cone_beta.append(0.0) print "j = {0:d} of {1:d} complete".format(j, len(data['pos.y'])) variable_list.append('cone_p') variable_list.append('cone_T') variable_list.append('cone_beta') variable_list.append('cone_theta') variable_list.append('cone_V') variable_list.append('eq_pitot_p') data['cone_p'] = cone_p data['cone_T'] = cone_T data['cone_beta'] = cone_beta data['cone_theta'] = cone_theta data['cone_V'] = cone_V data['eq_pitot_p'] = eq_pitot_p return data, variable_list
Demonstration of using the library functions to compute flow conditions across an oblique shock in equilibrium air. Data are chosen to match examples from Hunt and Souders NASA-SP-3093. PJ, 01-Jan-2014 """ from math import pi import sys, os sys.path.append(os.path.expandvars("$HOME/e3bin")) from cfpylib.gasdyn.cea2_gas import Gas from cfpylib.gasdyn.gas_flow import theta_oblique, beta_oblique print "Example 1: Hunt and Souders Table VIII, sub-table (j)" print "Given shock angle, compute post-shock conditions." s1 = Gas({'Air': 1.0}) s1.set_pT(52.671, 268.858) print "Initial gas state:" s1.write_state(sys.stdout) beta = 45.0 * pi / 180 # shock angle V1 = 7.9248e3 theta, V2, s2 = theta_oblique(s1, V1, beta) print( "Following oblique shock, beta=%g degrees, theta=%g degrees (Hunt&Souders 45 40.638)" % (beta * 180 / pi, theta * 180 / pi)) s2.write_state(sys.stdout) print "Across shock:" print "p2/p1=%g, T2/T1=%g (Hunt&Souders: 376.84 21.206)" % (s2.p / s1.p, s2.T / s1.T) print "\nExample 2: Hunt and Souders Table VI, sub-table (a)" print "Given deflection angle, compute shock angle and then post-shock conditions."
def run_pitot_differing_compression_ratio_analysis(cfg={}, config_file=None, force_restart=False): """ Chris James ([email protected]) 23/12/14 run_pitot_differing_compression_ratio_analysis(dict) - > depends force_restart can be used to force the simulation to start again instead of looking for an unfinished simulation that may be there... """ #---------------------- get the inputs set up -------------------------- if config_file: cfg = config_loader(config_file) # some dummy stuff here to pass the initial pitot input test if values are not there cfg['facility'] = 'custom' cfg['compression_ratio'] = 10.0 #----------------- check inputs ---------------------------------------- cfg = input_checker(cfg) cfg = check_new_inputs(cfg) intermediate_filename = cfg[ 'filename'] + '-differing-compression-ratio-analysis-intermediate-result-pickle.dat' # now check if we have attempted an old run before or not.... if not os.path.isfile(intermediate_filename) or force_restart: # if not, we set up a new one... # clean up any old files if the user has asked for it if cfg['cleanup_old_files']: # build a list of auxiliary files and run the cleanup_old_files function frim pitot_condition_builder.py auxiliary_file_list = [ '-differing-compression-ratio-analysis-log-and-result-files.zip', '-differing-compression-ratio-analysis-final-result-pickle.dat', '-differing-compression-ratio-analysis-normalised.csv', '-differing-compression-ratio-analysis-summary.txt', '-differing-compression-ratio-analysis.csv' ] cleanup_old_files(auxiliary_file_list) cleanup_old_files() # make a counter so we can work out what test we're running # also make one to store how many runs are successful cfg['number_of_test_runs'] = calculate_number_of_test_runs(cfg) # we use this variable to try to speed some things up later on cfg['last_run_successful'] = None import copy cfg['original_filename'] = copy.copy(cfg['filename']) # I think install the original cfg too cfg['cfg_original'] = copy.copy(cfg) # print the start message cfg = start_message(cfg) # work out what we need in our results dictionary and make the dictionary results = build_results_dict(cfg) # build the dictionary with the details of all the tests we want to run... test_condition_input_details = build_test_condition_input_details_dictionary( cfg) # so we can make the first run check the time... cfg['have_checked_time'] = False # counter to count the experiments that don't fail cfg['good_counter'] = 0 # list that will be filled by the experiment numbers as they finish... cfg['finished_simulations'] = [] else: # we can load an unfinished simulation and finishing it...! print '-' * 60 print "It appears that an unfinished simulation was found in this folder" print "We are now going to attempt to finish this simulation." print "If this is not what you want, please delete the file '{0}' and run the condition builder again.".format( intermediate_filename) import pickle with open(intermediate_filename, "rU") as data_file: intermediate_result = pickle.load(data_file) cfg = intermediate_result['cfg'] results = intermediate_result['results'] test_condition_input_details = intermediate_result[ 'test_condition_input_details'] data_file.close() #now start the main for loop for the simulation... for test_name in test_condition_input_details['test_names']: # this is for a re-loaded simulation, to make sure we don't re-run what has already been run if test_name in cfg['finished_simulations']: print '-' * 60 print "test_name '{0}' is already in the 'finished_simulations' list.".format( test_name) print "Therefore it has probably already been run and will be skipped..." continue # code to skip iteration # first set the test number variable... cfg['test_number'] = test_name # then set the details that every simulation will have cfg['compression_ratio'] = test_condition_input_details[test_name][ 'compression_ratio'] # if 'specified_pressure' is 'driver_p' we don't need to do anything here, # if it's 'p4' we need to get the related 'driver_p' if cfg['specified_pressure'] == 'p4': from cfpylib.gasdyn.cea2_gas import Gas primary_driver = Gas(cfg['driver_composition'], inputUnits=cfg['driver_inputUnits'], outputUnits=cfg['driver_inputUnits']) primary_driver.set_pT(101300.0, 300.0) pressure_ratio = cfg[ 'compression_ratio']**primary_driver.gam #pressure ratio is compression ratio to the power of gamma cfg['driver_p'] = cfg['p4'] / pressure_ratio run_status, results = differing_compression_ratio_analysis_test_run( cfg, results) if run_status: cfg['good_counter'] += 1 # add this to finished simulations list, regardless of whether it finished correctly or not... cfg['finished_simulations'].append(test_name) # now pickle the intermediate result so we can result the simulation if needed... pickle_intermediate_data(cfg, results, test_condition_input_details, filename=intermediate_filename) # now that we're done we can dump the results in various ways using tools from pitot condition builder... intro_line = "Output of pitot differing compression ratio analysis program Version {0}."\ .format(VERSION_STRING) results_csv_builder(results, test_name=cfg['original_filename'], intro_line=intro_line, filename=cfg['original_filename'] + '-differing-compression-ratio-analysis.csv') #and a normalised csv also normalised_results_csv_builder( results, cfg, test_name=cfg['original_filename'], intro_line=intro_line, normalised_by=cfg['normalise_results_by'], filename=cfg['original_filename'] + '-differing-compression-ratio-analysis-normalised.csv', extra_normalise_exceptions=['compression_ratio']) pickle_result_data( cfg, results, filename=cfg['original_filename'] + '-differing-compression-ratio-analysis-final-result-pickle.dat') # now delete the intermediate pickle that we made during the simulation... print "Removing the final intermediate pickle file." if os.path.isfile(intermediate_filename): os.remove(intermediate_filename) # now analyse results dictionary and print some results to the screen # and another external file differing_compression_ratio_analysis_summary(cfg, results) #zip up the final result using the function from pitot_condition_builder.py zip_result_and_log_files( cfg, output_filename=cfg['original_filename'] + '-differing-compression-ratio-analysis-log-and-result-files.zip') return
def main(): print "Helium driver gas" state4 = Gas({'He':1.0}) state4.set_pT(30.0e6, 3000.0) print "state4:" state4.write_state(sys.stdout) # print "Air driven gas" state1 = Gas({'Air':1.0}) state1.set_pT(30.0e3, 300.0) print "state1:" state1.write_state(sys.stdout) # print "\nNow do the classic shock tube solution..." # For the unsteady expansion of the driver gas, regulation of the amount # of expansion is determined by the shock-processed test gas. # Across the contact surface between these gases, the pressure and velocity # have to match so we set up some trials of various pressures and check # that velocities match. def error_in_velocity(p3p4, state4=state4, state1=state1): "Compute the velocity mismatch for a given pressure ratio across the expansion." # Across the expansion, we get a test-gas velocity, V3g. p3 = p3p4*state4.p V3g, state3 = finite_wave_dp('cplus', 0.0, state4, p3) # Across the contact surface. p2 = p3 print "current guess for p3 and p2=", p2 V1s, V2, V2g, state2 = normal_shock_p2p1(state1, p2/state1.p) return (V3g - V2g)/V3g p3p4 = secant(error_in_velocity, 0.1, 0.11, tol=1.0e-3) print "From secant solve: p3/p4=", p3p4 print "Expanded driver gas:" p3 = p3p4*state4.p V3g, state3 = finite_wave_dp('cplus', 0.0, state4, p3) print "V3g=", V3g print "state3:" state3.write_state(sys.stdout) print "Shock-processed test gas:" V1s, V2, V2g, state2 = normal_shock_p2p1(state1, p3/state1.p) print "V1s=", V1s, "V2g=", V2g print "state2:" state2.write_state(sys.stdout) assert abs(V2g - V3g)/V3g < 1.0e-3 # # Make a record for plotting against the Eilmer3 simulation data. # We reconstruct the expected data along a tube 0.0 <= x <= 1.0 # at t=100us, where the diaphragm is at x=0.5. x_centre = 0.5 # metres t = 100.0e-6 # seconds fp = open('exact.data', 'w') fp.write('# 1:x(m) 2:rho(kg/m**3) 3:p(Pa) 4:T(K) 5:V(m/s)\n') print 'Left end' x = 0.0 fp.write('%g %g %g %g %g\n' % (x, state4.rho, state4.p, state4.T, 0.0)) print 'Upstream head of the unsteady expansion.' x = x_centre - state4.a * t fp.write('%g %g %g %g %g\n' % (x, state4.rho, state4.p, state4.T, 0.0)) print 'The unsteady expansion in n steps.' n = 100 dp = (state3.p - state4.p) / n state = state4.clone() V = 0.0 p = state4.p for i in range(n): rhoa = state.rho * state.a dV = -dp / rhoa V += dV p += dp state.set_ps(p, state4.s) x = x_centre + t * (V - state.a) fp.write('%g %g %g %g %g\n' % (x, state.rho, state.p, state.T, V)) print 'Downstream tail of expansion.' x = x_centre + t * (V3g - state3.a) fp.write('%g %g %g %g %g\n' % (x, state3.rho, state3.p, state3.T, V3g)) print 'Contact surface.' x = x_centre + t * V3g fp.write('%g %g %g %g %g\n' % (x, state3.rho, state3.p, state3.T, V3g)) x = x_centre + t * V2g # should not have moved fp.write('%g %g %g %g %g\n' % (x, state2.rho, state2.p, state2.T, V2g)) print 'Shock front' x = x_centre + t * V1s # should not have moved fp.write('%g %g %g %g %g\n' % (x, state2.rho, state2.p, state2.T, V2g)) fp.write('%g %g %g %g %g\n' % (x, state1.rho, state1.p, state1.T, 0.0)) print 'Right end' x = 1.0 fp.write('%g %g %g %g %g\n' % (x, state1.rho, state1.p, state1.T, 0.0)) fp.close() return
def poshax_python( p_inf, T_inf, reactants, inputUnits, species_list, no_of_temperatures, reaction_file, outputUnits='massf', u_inf=None, M_inf=None, energy_exchange_file=None, gas_model_file='gas_model', cfg_file='cfg_file.cfg', output_file='output_file.data', source_term_coupling='loose', dx=1.0e-16, adaptive_dx='false', final_x=5.0e-3, plot=True, save_figures=True, temp_result_fig_name='temp_result', species_result_fig_name='species_result', freestream_normalised_result_fig_name='freestream_normalised_result', eq_normalised_result_fig_name='equilibrium_normalised_result', title=None, log_x_axis=False, plot_x_limits=None): """ :param p_inf: Freestream pressure (Pa) :param T_inf: Freestream temperature (K) :param reactants: Reactants dictionary which will be sent to cfypylib's cea2 Gas object. So it must conform with the required syntax!! an example is {'N2':0.79,'O2':0.21} for 'cfd air' specified by moles, but check the cea2_gas info for more info about the required format. inputUnits for this dictionary can be either in 'moles' or 'massf' with the standard specified in the next input :param inputUnits: inputUnits string to be again set to the cea2 Gas object. Should be either 'moles' or 'massf' :param species_list: Species list for POSHAX!! NOT CEA!! so ions and electons will be N2_plus, e_minus This data will be parsed to cea as the input state will only use these species, but the code will automatically perform the conversion need to send this list to CEA. This list is for sending to the program whcih creates the gas file for poshax :param no_of_temperatures: Number of temperatures. Integer input of 1,2, or 3. More than one temperature models require the specification of an energy exchange .lua file. (See below.) :param reaction_file: String pointing to the location of the reaction scheme. :param u_inf: Freestream velocity (m/s). Defaults to None, but either u_inf or M_inf must be specified. :param M_inf: Freestream Mach number. Defaults to None, but either u_inf or M_inf must be specified. :param energy_exchange_file: String pointing to the location of the energy exchange scheme for multi temperature models. Defaults to None, but the code will not run with a multi temperature model if this file is not found. :param gas_model_file: String name of the gas model file which the code will create as a .inp file which the program gasfile uses to make the .lua gasfile for poshax. Defaults to 'gas_model'. :param cfg_file: String name of the poshax cfg file. Defaults to 'cfg_file.cfg'. :param output_file: String name of the poshax output file. Defaults to 'output_file.data'. :param source_term_coupling: poshax source term coupling file. Defaults to 'loose'. :param dx: poshax dx (in m) defaults to 1.0e-16 m. :param adaptive_dx: Whether to use adaptive_dx with poshax. Defaults to 'true'. :param final_x: poshax final x value (in m). Defaults to 5.0e-3. :param plot: Whether to plot the result or not. Defaults to True. Will make four plots. A temperature plot, mass fraction plot of all species, result normalised by freestream values, and a result normalised by equilibrium values. :param save_figures: Whether to save figures or not. Defaults to True. results are saved in eps, png, and pdf formats. :param temp_result_fig_name: Temperature result plot file name. Defaults to 'temp_result'. :param species_result_fig_name: Species result plot file name. Defaults to 'species_result'. :param freestream_normalised_result_fig_name: Freestream normalised results plot file name. Defaults to 'freestream_normalised_result'. :param eq_normalised_result_fig_name: Equilibrium normalised results plot file name. Defaults to 'equilibrium_normalised_result'. :param title: allows a title to be added to the plots. Defaults to None. Is a single title so, for example, the condition being simulated can be added to the title. :param log_x_axis: Plots the figure with the x axis on a log scale. Defaults to False. plot_x_limits = None :return: Returns a Python dictionary version of the poshax results file. """ print '-' * 60 print "Running poshax python version {0}".format(VERSION_STRING) # do any input checking which is required # probably don't need to do everything in the world here, # but just some important things... # probably want to check that p, T, and u values, if not isinstance(p_inf, (float, int)): raise Exception, "poshax_python(): 'p_inf' input ({0}) is not a float or an int.".format( p_inf) if not isinstance(T_inf, (float, int)): raise Exception, "poshax_python(): 'T_inf' input ({0}) is not a float or an int.".format( T_inf) if not u_inf and not M_inf: raise Exception, "poshax_python(): Simulation must have either a 'u_inf' or 'M_inf' value." if u_inf and M_inf: raise Exception, "poshax_python(): Simulation cannot have have both a 'u_inf' and an 'M_inf' value." if u_inf and not isinstance(u_inf, (float, int)): raise Exception, "poshax_python(): 'u_inf' input ({0}) is not a float or an int.".format( u_inf) if M_inf and not isinstance(M_inf, (float, int)): raise Exception, "poshax_python(): 'M_inf' input ({0}) is not a float or an int.".format( M_inf) if not isinstance(reactants, dict): raise Exception, "poshax_python(): 'reactants' input ({0}) is not a dictionary.".format( reactants) if inputUnits not in ['moles', 'massf']: raise Exception, "poshax_python(): 'inputUits' input ({0}) is not either 'moles' or 'massf'.".format( inputUnits) if not isinstance(reaction_file, str): raise Exception, "poshax_python(): 'reaction_file' input ({0}) is not a string.".format( reaction_file) if not os.path.exists(reaction_file): raise Exception, "poshax_python(): 'reaction_file' input ({0}) does not appear to exist in the specified location.".format( reaction_file) if no_of_temperatures > 1 and not energy_exchange_file: raise Exception, "poshax_python(): Multi temperature model has been specified without an energy exchange file." if no_of_temperatures > 1: if not isinstance(energy_exchange_file, str): raise Exception, "poshax_python(): 'energy_exchange_file:' input ({0}) is not a string.".format( energy_exchange_file) if not os.path.exists(energy_exchange_file): raise Exception, "poshax_python(): 'reaction_file' input ({0}) does not appear to exist in the specified location.".format( energy_exchange_file) if not isinstance(dx, float): raise Exception, "poshax_python(): 'dx' input ({0}) is not a float.".format( dx) if not isinstance(final_x, float): raise Exception, "poshax_python(): 'final_x' input ({0}) is not a float.".format( final_x) print '-' * 60 print "User specified input values are:" if no_of_temperatures == 1: model = "thermally perfect gas" elif no_of_temperatures == 2: model = "two temperature gas" elif no_of_temperatures == 3: model = "three temperature gas" print "Gas state input settings:" print "p_inf = {0} Pa, T_inf = {1} K, u_inf = {2} m/s".format( p_inf, T_inf, u_inf) print "Reactants for start of CEA inflow calculation are:" print '{0} (by {1})'.format(reactants, inputUnits) print "Species in the calculation are:" print species_list print "Note: the CEA equilibrium inflow calculation will use only these species." print "This means that the inflow may be unrealistic if the species do not match " print "the real state of the gas at the user specified temperature and pressure" print '-' * 60 print "poshax settings:" print "dx = {0} m, final_x = {1} m ({2} mm), with adaptive_dx set to {3}".format( dx, final_x, final_x * 1000, adaptive_dx) print "calculation uses a {0} model, with {1} source term coupling.".format( model, source_term_coupling) print "Reaction scheme file is {0}".format(reaction_file) if no_of_temperatures > 1: print "Energy exchange file is {0}.".format(energy_exchange_file) print "Gas model file to be created will be called {0}.lua.".format( gas_model_file) print "poshax input file to be created will be called {0}.".format( cfg_file) print "poshax output / results file to be created will be called {0}".format( output_file) # as cea wants a slightly different form from poshax... species_list_for_cea = [] for species in species_list: if '_plus' in species: # copy the original species here so we don't mess with the old one!! species_copy = copy.copy(species) species_list_for_cea.append(species_copy.replace('_plus', '+')) elif '_minus' in species: # copy the original species here so we don't mess with the old one!! species_copy = copy.copy(species) species_list_for_cea.append(species_copy.replace('_minus', '-')) else: # just append the value... species_list_for_cea.append(species) print '-' * 60 print "Creating user specified equilibrium gas inflow using CEA:" # get mass fractions from CEA as poshax needs massf inflow state1 = Gas(reactants=reactants, with_ions=True, inputUnits=inputUnits, outputUnits=outputUnits, onlyList=species_list_for_cea) state1.set_pT(p_inf, T_inf) #Pa, K # print state to the screen... state1.write_state(sys.stdout) print '-' * 60 print "Preparing the gas .inp file from user specifications..." with open('{0}.inp'.format(gas_model_file), 'w') as gas_file: gas_file.write('model = "{0}"'.format(model) + '\n') species_line = 'species = {' for species in species_list: if species != species_list[-1]: species_line += "'{0}', ".format(species) else: species_line += "'{0}'".format(species) # now add the end bracket... species_line += '}' gas_file.write(species_line + '\n') gas_file.close() print "Running gasfile program to generate .lua gasfile." subprocess.check_call([ "gasfile", "{0}.inp".format(gas_model_file), "{0}.lua".format(gas_model_file) ]) print "Gas file prepared successfully." # now we need to build the .cfg file... print '-' * 60 print "Building poshax .cfg file from user specifications" with open(cfg_file, 'w') as cfg_file_ojbect: # add in all of the controls... cfg_file_ojbect.write('[controls]' + '\n') cfg_file_ojbect.write( 'source_term_coupling = {0}'.format(source_term_coupling) + '\n') cfg_file_ojbect.write('dx = {0}'.format(dx) + '\n') if adaptive_dx: # remember that a value of 'false' would still be a string... cfg_file_ojbect.write('adaptive_dx = {0}'.format(adaptive_dx) + '\n') cfg_file_ojbect.write('final_x = {0}'.format(final_x) + '\n') cfg_file_ojbect.write('output_file = {0}'.format(output_file) + '\n') cfg_file_ojbect.write('species_output = {0}'.format(outputUnits) + '\n') # the models... cfg_file_ojbect.write('[models]' + '\n') # a version of this without the .lua was defined above... cfg_file_ojbect.write( 'gas_model_file = {0}.lua'.format(gas_model_file) + '\n') cfg_file_ojbect.write('reaction_file = {0}'.format(reaction_file) + '\n') if no_of_temperatures > 1: cfg_file_ojbect.write( 'energy_exchange_file = {0}'.format(energy_exchange_file) + '\n') # initial conditions... cfg_file_ojbect.write('[initial-conditions]' + '\n') cfg_file_ojbect.write('p_inf = {0}'.format(p_inf) + '\n') if no_of_temperatures == 1: cfg_file_ojbect.write('T_inf = {0}'.format(T_inf) + '\n') elif no_of_temperatures == 2: cfg_file_ojbect.write('T_inf = {0}, {0}'.format(T_inf) + '\n') elif no_of_temperatures == 3: cfg_file_ojbect.write('T_inf = {0}, {0}, {0}'.format(T_inf) + '\n') if u_inf: cfg_file_ojbect.write('u_inf = {0}'.format(u_inf) + '\n') if M_inf: cfg_file_ojbect.write('M_inf = {0}'.format(M_inf) + '\n') # now go through species and we'll make a species line if outputUnits == 'moles': species_line = 'molef_inf = ' elif outputUnits == 'massf': species_line = 'massf_inf = ' for species in species_list: if species in state1.species: if species != species_list[-1]: species_line += '{0}, '.format(state1.species[species]) else: species_line += '{0}'.format(state1.species[species]) else: if species != species_list[-1]: species_line += '0.0, ' else: species_line += '0.0' cfg_file_ojbect.write(species_line + '\n') cfg_file_ojbect.close() print "Cfg file building successfully" print '-' * 60 print "Now running poshax program..." exit_code = subprocess.check_call(["poshax3.x", cfg_file]) if exit_code == 0: print "poshax ran successfully" else: print "poshax appears to have had some issues. check your inputs!" print '-' * 60 print "Performing equilibrium normal shock calculation to compare with poshax result." state2 = state1.clone() if not u_inf: u_inf = M_inf * state1.a V2, V2g = normal_shock(state1, u_inf, state2) print "Post-shock equilibrium state is :" print "post-shock velocity = {0} m/s".format(V2) state2.write_state(sys.stdout) # now opening the result so it can be returned... no_of_species = len(species_list) species_start_line = 6 + no_of_temperatures # 2 intro lines, x, p, rho, u lines_to_skip = 6 + no_of_temperatures + no_of_species # 2 intro lines, x, p, rho, u # I wanted to call my variable output_file (I nromally called the string output_filename) # but wanred to try to keep my variables consistent with poshax... so now I have results_file! with open(output_file, "r") as results_file: results_dict = {} columns = [] output_species_list = [] # start by grabbing columns names which wek now columns.append('x') if no_of_temperatures == 1: T_list = ['T'] elif no_of_temperatures == 2: T_list = ['T_trans_rotational', 'T_vibrational_electronic'] elif no_of_temperatures == 3: T_list = [ 'T_trans_rotational', 'T_vibrational_electronic', 'T_electron' ] columns += T_list columns += ['p', 'rho', 'u'] # we'll get the rest of the columns from species... for i, row in enumerate(results_file): if i >= species_start_line and i < lines_to_skip: # we could just get the species here, but good to keep it this way # so we can abstract the plotting into a function in the future... # (i.e. so it could run with no knowledge of the species beforehand, just the number of them... # here we want to split the line about the '-' so we can get the final value # need to use .strip() to get rid of the new line character on teh end too... split_row = row.strip().split('-') # last should be what we want... columns.append(split_row[-1]) output_species_list.append(split_row[-1]) elif i >= lines_to_skip: # to skip the header and know when the data starts... if i == lines_to_skip: # we need to add our columns to the dictionary! for column in columns: results_dict[column] = [] # now we start filling in data... split_row = row.split() # go through the split row and fill the data away in the right columns.. for column, value in zip(columns, split_row): results_dict[column].append(float(value)) results_dict['output_species_list'] = output_species_list if plot: print '-' * 60 print "Now plotting the result" import matplotlib.pyplot as plt import numpy as np # this is from shot_class_plotter.py, even if we don't use all of these... font_sizes = { 'title_size': 18, 'label_size': 18, 'annotation_size': 10, 'legend_text_size': 12, 'tick_size': 13, 'marker_size': 7, 'main_title_size': 20, 'marker': 'o', 'capsize': 4 } #--------------------------------------------------------------------------- # temp plot fig, ax = plt.subplots() x_list = np.array(results_dict['x']) * 1000.0 # convert to mm for T in T_list: ax.plot(x_list, results_dict[T], label=T) # add eq temp from CEA... ax.plot([x_list[0], x_list[-1]], [state2.T, state2.T], label='eq T from CEA') ax.set_xlabel('post-shock distance (mm)') ax.set_ylabel('temperature (K)') if log_x_axis: ax.set_xscale('log') if plot_x_limits: ax.set_xlim(plot_x_limits) plt.legend(loc='best') if title: plt.title(title) if save_figures: plt.savefig(temp_result_fig_name + '.png', dpi=300) plt.savefig(temp_result_fig_name + '.eps', dpi=300) plt.savefig(temp_result_fig_name + '.pdf', dpi=300) plt.show() #------------------------------------------------------------------------- # now do massf plot species_fig, species_ax = plt.subplots() for i, species in enumerate(output_species_list): # use solid lines if we are line 7 or less # change over for lines past this # this gives maximum 12 species, I guess, before overlap # works with earlier version of matplotlib will only 7 colours too.. if i < 7: linestyle = '-' else: linestyle = '--' # change the _plus or _minus to + or - here to make the labels nicer... if '_plus' in species: species_label = species.replace('_plus', '+') elif '_minus' in species: species_label = species.replace('_minus', '-') else: species_label = species species_ax.plot(x_list, results_dict[species], linestyle=linestyle, label=species_label) species_ax.set_xlabel('post-shock distance (mm)') if outputUnits == 'moles': species_ax.set_ylabel('moles per original mole') elif outputUnits == 'massf': species_ax.set_ylabel('mass fraction of species') if log_x_axis: species_ax.set_xscale('log') if plot_x_limits: species_ax.set_xlim(plot_x_limits) plt.legend(loc='best') if title: plt.title(title) if save_figures: plt.savefig(species_result_fig_name + '.png', dpi=300) plt.savefig(species_result_fig_name + '.eps', dpi=300) plt.savefig(species_result_fig_name + '.pdf', dpi=300) plt.show() # ------------------------------------------------------------------- # now do the freestream normalised plot freestream_normalised_fig, freestream_normalised_ax = plt.subplots() for T in T_list: freestream_normalised_ax.plot(x_list, np.array(results_dict[T]) / T_inf, label=T) # now do p, rho, and u # I commented out pressure here as it changes by too much and messes with the plot #freestream_normalised_ax.plot(x_list, np.array(results_dict['p'])/p_inf, label = 'p') # have to get rho from state 1 as we didn't need to specify it for inflow calculation... freestream_normalised_ax.plot(x_list, np.array(results_dict['rho']) / state1.rho, label='rho') freestream_normalised_ax.plot(x_list, np.array(results_dict['u']) / u_inf, label='u') # add eq values from CEA freestream_normalised_ax.plot([x_list[0], x_list[-1]], [state2.T / T_inf, state2.T / T_inf], label='eq T from CEA') freestream_normalised_ax.plot( [x_list[0], x_list[-1]], [state2.rho / state1.rho, state2.rho / state1.rho], label='eq rho from CEA') freestream_normalised_ax.plot([x_list[0], x_list[-1]], [V2 / u_inf, V2 / u_inf], label='eq u from CEA') freestream_normalised_ax.set_xlabel('post-shock distance (mm)') freestream_normalised_ax.set_ylabel( 'quantity normalised by freestream value') if log_x_axis: freestream_normalised_ax.set_xscale('log') if plot_x_limits: freestream_normalised_ax.set_xlim(plot_x_limits) plt.legend(loc='best') if title: plt.title(title) if save_figures: plt.savefig(freestream_normalised_result_fig_name + '.png', dpi=300) plt.savefig(freestream_normalised_result_fig_name + '.eps', dpi=300) plt.savefig(freestream_normalised_result_fig_name + '.pdf', dpi=300) plt.show() #------------------------------------------------------------------------ # now do the equilibrium normalised plot eq_normalised_fig, eq_normalised_ax = plt.subplots() for T in T_list: eq_normalised_ax.plot(x_list, np.array(results_dict[T]) / state2.T, label=T) # now do p, rho, and u eq_normalised_ax.plot(x_list, np.array(results_dict['p']) / state2.p, label='p') eq_normalised_ax.plot(x_list, np.array(results_dict['rho']) / state2.rho, label='rho') eq_normalised_ax.plot(x_list, np.array(results_dict['u']) / V2, label='u') eq_normalised_ax.set_xlabel('post-shock distance (mm)') eq_normalised_ax.set_ylabel( 'quantity normalised by equilibrium value from CEA') if log_x_axis: eq_normalised_ax.set_xscale('log') if plot_x_limits: eq_normalised_ax.set_xlim(plot_x_limits) plt.legend(loc='best') if title: plt.title(title) if save_figures: plt.savefig(eq_normalised_result_fig_name + '.png', dpi=300) plt.savefig(eq_normalised_result_fig_name + '.eps', dpi=300) plt.savefig(eq_normalised_result_fig_name + '.pdf', dpi=300) plt.show() return results_dict
def main(): #build some gas objects print "pure He driver gas" state4 = Gas({'He': 1.0}, inputUnits='moles', outputUnits='moles') state4.set_pT(2.79e+07, 2700.0) print "state1:" state4.write_state(sys.stdout) print "Air test gas" state1 = Gas({ 'Air': 1.0, }) state1.set_pT(3000.0, 300.0) print "state1:" state1.write_state(sys.stdout) # print "Air accelerator gas" state5 = Gas({'Air': 1.0}) state5.set_pT(10.0, 300.0) print "state10:" state5.write_state(sys.stdout) print "Steady expansion of driver" #for 100%He condition mach number terminating steady expansion is 2.15 #need to work out the corresponding pressure for this to put into #the function M4dash = 2.15 #mach number terminating steady expansion (state4dash, V4dash) = expand_from_stagnation(1.0 / (p0_p(M4dash, state4.gam)), state4) print "state4dash:" state4dash.write_state(sys.stdout) print "V4dash = {0} m/s".format(V4dash) #I think we need to start guessing shock speeds to be able to keep going here print "Start working on the shock into the test gas" print "Guess US1 of 5080 m/s" state2 = state1.clone() V2, V2g = normal_shock(state1, 5080.0, state2) print "state2:" state2.write_state(sys.stdout) print "Checks:" print "p2/p1=", state2.p / state1.p print "rho2/rho1=", state2.rho / state1.rho print "T2/T1=", state2.T / state1.T print "V2g = {0} m/s".format(V2g) print "Unsteady expansion from state 4dash to state 3" print "Well, giving it a go for now anyway" (V3, state3) = finite_wave_dp('cplus', V4dash, state4dash, state2.p, steps=100) print "state3:" state3.write_state(sys.stdout) print "V3 = {0} m/s".format(V3) print "Start working on the shock into the accelerator gas" print "Guess US2 of 11100 m/s" state6 = state5.clone() V6, V6g = normal_shock(state5, 11100.0, state6) print "state6:" state6.write_state(sys.stdout) print "Checks:" print "p2/p1=", state6.p / state5.p print "rho2/rho1=", state6.rho / state5.rho print "T2/T1=", state6.T / state5.T print "V6g = {0} m/s".format(V6g) print "Unsteady expansion from state 2 to state 7" print "Well, giving it a go for now anyway" (V7, state7) = finite_wave_dp('cplus', V2g, state2, state6.p, steps=100) print "state7:" state7.write_state(sys.stdout) print "V7 = {0} m/s".format(V7) M7 = V7 / (state7.gam * state7.R * state7.T)**(0.5) print "M7 = {0}".format(M7) """
def main(): print "Titan gas" state1 = Gas({ 'N2': 0.95, 'CH4': 0.05 }, inputUnits='moles', outputUnits='moles') state1.set_pT(2600.0, 300.0) print "state1:" state1.write_state(sys.stdout) # print "Air accelerator gas" state10 = Gas({'Air': 1.0}) state10.set_pT(10.0, 300.0) print "state10:" state10.write_state(sys.stdout) # print "Incident shock" state2 = state1.clone() V2, V2g = normal_shock(state1, 4100.0, state2) print "V2=", V2, "Vg=", V2g, "expected 3670.56" print "state2:" state2.write_state(sys.stdout) print "Checks:" print "p2/p1=", state2.p / state1.p, "expected 166.4" print "rho2/rho1=", state2.rho / state1.rho, "expected 9.5474" print "T2/T1=", state2.T / state1.T, "expected 14.9" # print "\nNow do unsteady expansion..." # For the unsteady expansion of the test gas, regulation of the amount # of expansion is determined by the shock-processed accelerator gas. # Across the contact surface between these gases, the pressure and velocity # have to match so we set up some trials of various pressures and check # that velocities match. def error_in_velocity(p5p2, state2=state2, V2g=V2g, state10=state10): "Compute the velocity mismatch for a given pressure ratio across the expansion." # Across the expansion, we get a test-gas velocity, V5g. V5g, state5 = finite_wave_dp('cplus', V2g, state2, p5p2 * state2.p) # Across the contact surface, p20 == p5 p20 = p5p2 * state2.p print "current guess for p5 and p20=", p20 V10, V20, V20g, state20 = normal_shock_p2p1(state10, p20 / state10.p) return ( V5g - V10 ) / V5g # V10 was V20g - lab speed of accelerator gas - we now make the assumption that this is the same as the shock speed p5p2 = secant(error_in_velocity, 0.01, 0.011, tol=1.0e-3) print "From secant solve: p5/p2=", p5p2 # It would have been faster and the code closer to Hadas' spreadsheet if we had # stepped down in pressure until we found the point where the velocities matched. # The expansion along the u+a wave would have appeared in the code here. V5g, state5 = finite_wave_dp('cplus', V2g, state2, p5p2 * state2.p) print "Expanded test gas, at end of acceleration tube:" print "V5g=", V5g print "state5:" state5.write_state(sys.stdout) V10, V20, V20g, state20 = normal_shock_p2p1(state10, state5.p / state10.p) print V10 print "Done." return