Mercurial > hg > cfcfd3
changeset 2601:c63df03b11a6
nenzfr: Add Luke's perturbation and sensitivity calculation scripts
author | Tamara Sopek <tamara.sopek@eng.ox.ac.uk> |
---|---|
date | Wed, 03 Jun 2020 20:53:51 +1000 |
parents | 0ab07fc77eef |
children | a639cb9fc338 |
files | app/nenzfr/nenzfr_stats.py app/nenzfr/nenzfr_utils.py app/nenzfr/perturb.py app/nenzfr/perturb_input_utils.py app/nenzfr/perturb_utils.py app/nenzfr/sensitivity.py app/nenzfr/sensitivity_input_utils.py app/nenzfr/sensitivity_utils.py |
diffstat | 8 files changed, 1371 insertions(+), 27 deletions(-) [+] |
line wrap: on
line diff
--- a/app/nenzfr/nenzfr_stats.py Tue May 05 11:13:34 2020 +1000 +++ b/app/nenzfr/nenzfr_stats.py Wed Jun 03 20:53:51 2020 +1000 @@ -8,6 +8,11 @@ Area and mass weighted statistics are also calculated and written out. 22-Aug-2012 Brute-force approach to estimating the CMME 1D flow conditions. +04-May-2020 + Function add_extra_variables (which is useful for uncertainty analysis) + is now included here. This is in preparation for the removal of + nenzfr_perturbed.py, nenzfr_sensitivity.py. These have been superceded + with perturb.py and sensitivity.py """ import sys, os @@ -66,6 +71,32 @@ return variable_list, data +def add_extra_variables(data, var): + """ + We add some additional variables that of are interest to + the input data dictionary and list. + + data: dictionary of data + var: list of variables + """ + U_squared = data['vel.x']*data['vel.x'] + data['vel.y']*data['vel.y'] + U = U_squared**0.5 + # Dynamic Pressure + data['q'] = 0.5 * data['rho'] * U_squared + var.append('q') + # Mass flow rate per unit area + data['m_dot'] = data['rho'] * U + var.append('m_dot') + # Unit Reynolds number + data['Re_u'] = data['m_dot'] / data['mu'] + var.append('Re_u') + # Pressure coefficient + data['p/q'] = data['p'] / data['q'] + var.append('p/q') + + return data, var + + def print_stats_MoA(sliceFileName,jobName,coreRfraction,weight): """ (MoA = Mass- or Area-weighted)
--- a/app/nenzfr/nenzfr_utils.py Tue May 05 11:13:34 2020 +1000 +++ b/app/nenzfr/nenzfr_utils.py Wed Jun 03 20:53:51 2020 +1000 @@ -3,9 +3,11 @@ """ import sys, os -import shlex, subprocess, string +import shlex, subprocess, string, copy E3BIN = os.path.expandvars("$HOME/e3bin") sys.path.append(E3BIN) +from nenzfr_stats import add_extra_variables + #--------------------------------------------------------------- @@ -67,32 +69,33 @@ # nenzfr_sensitivity.py # nenzfr_RSA.py -def read_case_summary(FileToRead=None): - """ - Reads the file "perturbation_cases.dat" to determine which variables - have been perturbed and values for the perturbed variables for each - case consituting the sensitivity calculation. - - :returns: perturbedVariables - a list of variable names - : DictOfCases - a dictionary with the case names as keys - and the values of each perturbed variable. - """ - if FileToRead is None: - fp = open('perturbation_cases.dat','r') - else: - fp = open(FileToRead,'r') - - varList = fp.readline().strip().split(" ") - perturbedVariables = [k for k in varList if k!="#" and k!=""] - fp.readline() - DictOfCases = {} - for line in fp.readlines(): - caseData = line.strip().split(" ") - caseName = caseData[0] - DictOfCases[caseName] = [float(k) for k in caseData \ - if k!=caseName and k!=""] - fp.close() - return perturbedVariables, DictOfCases +# 04-May-2020: read_case_summary now included in perturb_utils.py +#def read_case_summary(FileToRead=None): +# """ +# Reads the file "perturbation_cases.dat" to determine which variables +# have been perturbed and values for the perturbed variables for each +# case consituting the sensitivity calculation. +# +# :returns: perturbedVariables - a list of variable names +# : DictOfCases - a dictionary with the case names as keys +# and the values of each perturbed variable. +# """ +# if FileToRead is None: +# fp = open('perturbation_cases.dat','r') +# else: +# fp = open(FileToRead,'r') +# +# varList = fp.readline().strip().split(" ") +# perturbedVariables = [k for k in varList if k!="#" and k!=""] +# fp.readline() +# DictOfCases = {} +# for line in fp.readlines(): +# caseData = line.strip().split(" ") +# caseName = caseData[0] +# DictOfCases[caseName] = [float(k) for k in caseData \ +# if k!=caseName and k!=""] +# fp.close() +# return perturbedVariables, DictOfCases def read_nenzfr_outfile(FileToRead,inclStats=0): """ @@ -164,6 +167,47 @@ fp.close() return supplyDict + +def read_output_for_sensitivity_analysis(FileToRead): + """ + A simple function that reads both nenzfr outfile and estcj + outfile and combines the results, returning a dictionary of + values and list of variables. + + It is intended that this function be imported into 'read_outfile.py', + which must be created by the user in order to run a sensitivity + analysis (using perturb.py and sensitivity.py + + It reads files: + FileToRead-exit.stats + FileToRead-estcj.dat + """ + dataDict = {} + varList = {} + + exitData, exitVar = \ + read_nenzfr_outfile(FileToRead+'-exit.stats') + supplyData = read_estcj_outfile(FileToRead+'-estcj.dat') + + dataDict = copy.deepcopy(exitData) + varList = copy.deepcopy(exitVar) + + # Add the relevant nozzle supply data + dataDict['supply_rho'] = supplyData['rho'] + dataDict['supply_T'] = supplyData['T'] + dataDict['supply_h'] = supplyData['h'] + + # Add the variables to the list + varList.insert(0,'supply_rho') + varList.insert(1,'supply_T') + varList.insert(2,'supply_h') + + # Add extra freestream properties of interest + dataDict, varList = add_extra_variables(dataDict, varList) + + return dataDict, varList + + #--------------------------------------------------------------- # The following is required by: # nenzfr_perturbed.py @@ -204,6 +248,7 @@ return scriptFileName, cfgFileName + def build_cfg_file(cfg, filename): """ Takes a nenzfr config dictionary and uses it to build a nenzfr config file.close
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/nenzfr/perturb.py Wed Jun 03 20:53:51 2020 +1000 @@ -0,0 +1,175 @@ +#!/usr/bin/env python +""" perturb.py + +This script coordinates the running of a series of calculations +that are perturbations around a nominal condition. + +.. Author: Luke Doherty (luke.doherty@eng.ox.ac.uk) + Oxford Thermofluids Institute + The University of Oxford +""" + +VERSION_STRING = "04-May-2020" + +import shlex, subprocess, string +from subprocess import PIPE +import sys, os, gzip +import optparse +from numpy import array, mean, logical_and, zeros +from perturb_input_utils import perturb_input_checker +from perturb_utils import run_command, \ + set_case_running, set_perturbed_values,\ + write_case_config, write_case_summary +import copy +E3BIN = os.path.expandvars("$HOME/e3bin") +sys.path.append(E3BIN) + +#--------------------------------------------------------------- +def run_perturb(cfg): + """ + Function that accepts a config dictionary and runs perturb. + """ + # + cfg['bad_input'] = False + # + # First check our input and assign any defaults values + cfg = perturb_input_checker(cfg) + #bail out here if there is an issue + if cfg['bad_input']: + return -2 + # + # Unpack the input dictionary of nominal values into the main + # configuration dictionary + for var in cfg['nominalValues'].keys(): + cfg[var] = cfg['nominalValues'][var] + # + # Re-assign the dictionary of perturbed values (for later convenience) + perturbedDict = cfg['perturbedDict'] + # + if not cfg['createRSA']: # Perturbing for a sensitivity calculation + # Calculate Nominal condition + caseString = 'case'+"{0:02}".format(0)+"{0:01}".format(0) + textString = "Nominal Condition" + caseDict = copy.copy(cfg) + caseDict['caseName'] = caseString + write_case_config(caseDict) + # Run the nominal case and write the values of the perturbed + # variables to a summary file + set_case_running(caseString, caseDict, textString,'') + write_case_summary(cfg['variablesToPerturb'],caseDict,caseString,1) + # + # Now run all the perturbed conditions + for k in range(len(cfg['variablesToPerturb'])): + var = cfg['variablesToPerturb'][k] + perturbCount = cfg['levels'] + + for kk in range(perturbCount): + if kk != 0: + caseString = 'case'+"{0:02}".format(k)+\ + "{0:01}".format(kk) + textString = var+" perturbed to "+\ + str(perturbedDict[var][kk]) + caseDict = copy.copy(cfg) + caseDict[var] = perturbedDict[var][kk] + caseDict['caseName'] = caseString + # Run the current case + set_case_running(caseString, caseDict, \ + textString, var) + # Write current case to the summary file + write_case_summary(cfg['variablesToPerturb'],\ + caseDict,caseString,0) + # + else: # Perturbing to create a Response Surface + # Currently we can only fit a response surface through + # two variables, so we take the first two in the input list + var1 = cfg['variablesToPerturb'][0] # 'Vs' + var2 = cfg['variablesToPerturb'][1] # 'pe' + + if cfg['levels'] == 2.5: + casesToRun = [(2,1), (1,1), + (0,0), + (2,2), (1,2)] + elif cfg['levels'] == 3: + casesToRun = [(2,1),(0,1),(1,1), + (2,0),(0,0),(1,0), + (2,2),(0,2),(1,2)] + elif cfg['levels'] == 5: + casesToRun = [(4,3), (0,3), (3,3), + (2,1), (1,1), + (4,0), (0,0), (3,0), + (2,2), (1,2), + (4,4), (0,4), (3,4)] + #casesToRun = [ (2,3), (1,3), + # (4,1), (0,1), (3,1), + # (2,0), (1,0), + # (4,2), (0,2), (3,2), + # (2,4), (1,4) ] + # + # Run the nominal case first + caseString = 'case'+"{0:01}{0:01}".format(0,0) + caseDict = copy.copy(paramDict) + caseDict['caseName'] = caseString + textString = "Nominal Case: "+var1+"="+str(perturbedDict[var1][0])+\ + "; "+var2+"="+str(perturbedDict[var2][0]) + write_case_config(caseDict) + set_case_running(caseString, caseDict, textString, '') + write_case_summary(cfg['variablesToPerturb'], caseDict, caseString, 1) + # + # Now run all other cases + for case in casesToRun: + if case != (0,0): + caseString = 'case'+"{0:01}{1:01}".format(case[0],case[1]) + textString = var1+" perturbed to "+\ + str(perturbedDict[var1][case[0]])+\ + "\n"+var2+" perturbed to "+\ + str(perturbedDict[var2][case[1]]) + caseDict = copy.copy(paramDict) + caseDict['caseName'] = caseString + caseDict[var1] = perturbedDict[var1][case[0]] + caseDict[var2] = perturbedDict[var2][case[1]] + # + set_case_running(caseString, caseDict, textString, '') + write_case_summary(cfg['variablesToPerturb'], \ + caseDict, caseString, 0) + # + return + + +def main(): + """ + Examine the command-line options to decide the what to do + and then coordinate a series of calculations using inputs + that are perturbed around specified nominal values. + """ + op = optparse.OptionParser(version=VERSION_STRING) + op.add_option('-c', '--config_file', dest='config_file', + help=("filename for the config file")) + opt, args = op.parse_args() + config_file = opt.config_file + # + cfg = {} + # + if not cfg: #if the configuration dictionary has not been filled up already, load it from a file + try: #from Rowan's onedval program + execfile(config_file, globals(), cfg) + except IOError as e: + print "Error {0}".format(str(e)) + print "There was a problem reading the config file: '{0}'".format(config_file) + print "Check that it conforms to Python syntax." + print "Bailing out!" + sys.exit(1) + # + run_perturb(cfg) + # + return + +#--------------------------------------------------------------- + +if __name__ == '__main__': + if len(sys.argv) <= 1: + print "perturb:\n run simulations that are perturbations about a nominal" + print " Version:", VERSION_STRING + print " To get some useful hints, invoke the program with option --help." + sys.exit(0) + return_flag = main() + sys.exit(return_flag)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/nenzfr/perturb_input_utils.py Wed Jun 03 20:53:51 2020 +1000 @@ -0,0 +1,139 @@ +""" +perturb_input_utils.py -- Function to check the perturb input dictionary + that is pulled in from a config file. Default values are added + where appropriate. + +.. Author: Luke Doherty (luke.doherty@eng.ox.ac.uk) + Oxford Thermofluids Institute + The University of Oxford + + Version: 04-May-2020 +""" + +from perturb_utils import set_perturbed_values + +def perturb_input_checker(cfg): + """Takes the config dictionary and checks that all the necessary + inputs are in there. Will bail out if important variables are missing, + and set default values for those that are not crucial. + + Returns the config dictionary after it has been checked over and will + tell perturb to bail out if it finds an issue. + + """ + + print "Checking perturb inputs." + # + if 'runCMD' not in cfg: + cfg['runCMD'] = './' + print "Run command not specified. We will assume you are running" + print " the simulations on a local machine that does not have" + print " a que. Setting to a default run command ('{0}')".format(cfg['runCMD']) + # + if 'levels' not in cfg: + cfg['levels'] = 3 + print "The number of perturbation levels to use is not specified." + print " Setting it to default value of {0}".format(cfg['levels']) + # + if 'createRSA' not in cfg: + print "Response surface approximation variable not set." + print " Setting it to False." + # + if cfg['levels'] == '3-reduced': + if cfg['createRSA']: + cfg['levels'] = 2.5 + else: + print "Setting levels to '3-reduced' is only valid when creating a" + print " response surface. Changing this to levels = 3." + cfg['levels'] = 3 + else: + cfg['levels'] = int(cfg['levels']) + # + if 'variablesToPerturb' not in cfg: + print "You have not specified a list of which variables are to be perturbed." + print " Bailing out." + cfg['bad_input'] = True + # + if 'nominalValues' not in cfg: + print "You have not specified a dictionary of default values for each variable" + print " that will be perturbed." + print " Bailing out." + cfg['bad_input'] = True + else: + for var in cfg['variablesToPerturb']: + if var not in cfg['nominalValues']: + print "{0} is not present in the nominalValues dictionary." + print " Bailing out." + cfg['bad_input'] = True + # + if 'typeOfPerturbation' not in cfg and 'perturbationMagnitudes' not in cfg: + print "Neither the typeOfPerturbation or perturbationMagnitudes dictionaries" + print " have been specified. Setting a default 'relative' perturbation of" + print " 1.0 percent for each variable." + cfg['typeOfPerturbation'] = {} + cfg['perturbationMagnitudes'] = {} + for var in cfg['variablesToPerturb']: + cfg['typeOfPerturbation'][var] = 'relative' + cfg['perturbationMagnitudes'][var] = [1.0] # This must a list... + elif 'typeOfPerturbation' not in cfg and 'perturbationMagnitudes' in cfg: + print "You must specify either both or neither of the typeOfPerturbation" + print " and perturbationMagnitudes dictionaries." + print " Bailing out." + cfg['bad_input'] = True + elif 'typeOfPerturbation' in cfg and 'perturbationMagnitudes' not in cfg: + print "You must specify either both or neither of the typeOfPerturbation" + print " and perturbationMagnitudes dictionaries." + print " Bailing out." + cfg['bad_input'] = True + else: + for var in cfg['variablesToPerturb']: + if var not in cfg['typeOfPerturbation']: + print "{0} is not present in typeOfPerturbation dictionary.".format(var) + print " Bailing out." + cfg['bad_input'] = True + if var not in cfg['perturbationMagnitudes']: + print "{0} is not present in perturbationMagnitudes dictionary.".format(var) + print " Bailing out." + cfg['bad_input'] = True + # + #if 'inputFileTemplate' not in cfg: + # print "You have not specified the inputFileTemplate file name." + # print " Bailing out." + # cfg['bad_input'] = True + # + if 'runScriptTemplate' not in cfg: + print "You have not specified the runScriptTemplate file name." + print " Bailing out." + cfg['bad_input'] = True + # + # Warn the user that only the first two elements of the input + # variablesToPerturb list will be perturbed when we want to + # create a response surface + if cfg['createRSA']: + print "Create Response Surface (RS) option selected." + if len(cfg['variablesToPerturb'])>2: + print "NOTE: We can currently only fit a response surface to" + print "two variables." + + if 'extraFilesToCopy' not in cfg: + print "No extra files to copy have specified." + cfg['extraFilesToCopy'] = [] + + # Now go through and calculate the perturbed values for each variable + # of interest. + if not cfg['bad_input']: + cfg['perturbedDict'] = {} #this will get populated below + # + for variable in cfg['variablesToPerturb']: + cfg['perturbedDict'][variable] = set_perturbed_values( \ + cfg['nominalValues'][variable], \ + cfg['perturbationMagnitudes'][variable],\ + cfg['typeOfPerturbation'][variable], \ + cfg['levels']) + + print "Done with perturbed variable {0}.".format(variable) + + print "Done checking perturb inputs." + + return cfg +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/nenzfr/perturb_utils.py Wed Jun 03 20:53:51 2020 +1000 @@ -0,0 +1,282 @@ +""" +perturb_utils.py -- Small untility functions needed by the + perturb and sensitivity programs. + +.. Author: Luke Doherty (luke.doherty@eng.ox.ac.uk) + Oxford Thermofluids Institute + The University of Oxford + + Version: 04-May-2020 +""" + +import sys, os +import shlex, subprocess, string +E3BIN = os.path.expandvars("$HOME/e3bin") +sys.path.append(E3BIN) + +#------------------------------------------------------------------------- +def run_command(cmdText): + """ + Run the command as a subprocess. Copied verbatim from nenzfr_utils.py + """ + + # Flush before using subprocess to ensure output is in the right order. + sys.stdout.flush() + + if (type(cmdText) is list): + args = cmdText + else: + args = shlex.split(cmdText) + print "About to run cmd:", string.join(args) + # p = subprocess.Popen(args) + # wait until the subprocess is finished + # stdoutData, stderrData = p.communicate() + return subprocess.check_call(args) + + +def set_case_running(caseString, caseDict, textString, var): + """ + A short function to set a given case running in an appropriately + named sub-directory. + """ + print 60*"-" + print caseString + print textString + # + if var not in ['coreRfraction','CoreRadiusFraction']: + # Create sub-directory for the current case + run_command('mkdir ./'+caseString) + # + # Set up the run script + scriptFileName = caseDict['runScriptTemplate'] + create_file_from_template(scriptFileName, './'+caseString+\ + '/'+scriptFileName, caseDict) + # + # Set up the input file + if 'inputFileTemplate' in caseDict: + inputFileName = caseDict['inputFileTemplate'] + create_file_from_template(inputFileName,'./'+caseString+\ + '/'+inputFileName, caseDict) + # + # If required, copy additional files to the sub-directory + for file in caseDict['extraFilesToCopy']: + command_text = 'cp ./'+file+' ./'+caseString+'/' + run_command(command_text) + # + # Change into the sub-directory, ensure the run script and + # input file are exectuable, then execute the run script + os.chdir(caseString) + run_command('chmod u+x '+scriptFileName) + if 'inputFileTemplate' in caseDict: + run_command('chmod u+x '+inputFileName) + print "" + print caseDict['runCMD']+scriptFileName + print "" + # I am not sure how to replace the following line with the + # run_command function + os.system(caseDict['runCMD']+scriptFileName) + os.chdir('../') + else: + # Below is very specific to nenzfr. I don't know how best to remove + # this so I've left it. LukeD 05-05-2020 + if not os.path.exists(caseString): + # Copy nominal case data into a new case directory + command_text = 'cp -r case000 '+caseString + run_command(command_text) + # + # Change into new directory and call nenzfr --just-stats + os.chdir(caseString) + command_text = \ + 'nenzfr.py --just-stats --CoreRadiusFraction='+\ + str(caseDict[var]) + #str(DictOfCases[caseString][perturbedVariables.index(var)]) + print "" + print command_text + run_command(command_text) + print "" + os.chdir('../') + # + return + +def create_file_from_template(templateFileName, outPutFileName, substituteDict): + """Function that will write out a new file by undertaking a dictionary + substitution on a nominated template file. + """ + # + fp = open(templateFileName, 'r') + text = fp.read() + fp.close() + template = string.Template(text) + text = template.safe_substitute(substituteDict) + fp = open(outPutFileName, 'w') + fp.write(text) + fp.close() + # + return + +def set_perturbed_values(NominalValue, PerturbationMagnitude, TypeOfPerturbation, Levels): + """ + + """ + if TypeOfPerturbation == "relative": + delta = [k/100.0*NominalValue for k in PerturbationMagnitude] + else: + delta = PerturbationMagnitude + # + if Levels == 3: + if len(delta) == 1: + # have [delta] + values = [NominalValue, NominalValue+delta[0],\ + NominalValue-delta[0]] + elif len(delta) == 2: + # have [+delta, -delta] + values = [NominalValue, NominalValue+delta[0],\ + NominalValue-delta[1]] + elif Levels == 5: + if len(delta) == 1: + # have [delta] + values = [NominalValue, NominalValue+delta[0],\ + NominalValue-delta[0],\ + NominalValue+2.0*delta[0],\ + NominalValue-2.0*delta[0]] + elif len(delta) == 2: + # have [delta, "2"*delta] + values = [NominalValue, NominalValue+delta[0],\ + NominalValue-delta[0],\ + NominalValue+delta[1],\ + NominalValue-delta[1]] + elif len(delta) == 3: + # have [+delta, -delta, "2"*delta] + values = [NominalValue, NominalValue+delta[0],\ + NominalValue-delta[1],\ + NominalValue+delta[2],\ + NominalValue-delta[2]] + elif len(data) == 5: + # have [+delta, -delta, +"2"*delta, -"2"*delta] + values = [NominalValue, NominalValue+delta[0],\ + NominalValue-delta[1],\ + NominalValue+delta[2],\ + NominalValue-delta[3]] + + return values + +def write_case_summary(varList,caseDict,caseString,newfile): + """ + A short function to write the values of the perturbed variables + for the current case to a summary file. + """ + # Define some format strings + #l = max( max([len(k) for k in varList]), 15 ) + #dataFormat = '{0:>'+str(l)+'.6g}' + #titleFormat = '{0:{fill}>'+str(l)+'}' + dataFormat = {k : '{0:>'+"{0}".format(max(11,len(k))+2)+".5g}" \ + for k in varList} + titleFormat = {k : '{0:{fill}>'+"{0}".format(max(11,len(k))+2)+"}" \ + for k in varList} + + # For the first time, we create a new file and write the header information. + # Each following case is appended to the existing file. + if newfile == 1: + fout = open('perturbation_cases.dat','w') + # Write title line + fout.write('{0:>7}'.format('#')) + for k in tuple(varList): + fout.write(titleFormat[k].format(k,fill='')) + fout.write('\n') + # Underline the title + for k in varList: + fout.write(titleFormat[k].format('-',fill='-')) + fout.write('{0:->7}'.format('-')) + fout.write('\n') + else: + fout = open('perturbation_cases.dat','a') + # Now write out the data for the current case + fout.write('{0:>7}'.format(caseString)) + for k in varList: + fout.write(dataFormat[k].format(caseDict[k])) + fout.write('\n') + fout.close() + +def read_case_summary(FileToRead=None): + """ + Reads the file "perturbation_cases.dat" to determine which variables + have been perturbed and values for the perturbed variables for each + case consituting the sensitivity calculation. + + :returns: perturbedVariables - a list of variable names + : DictOfCases - a dictionary with the case names as keys + and the values of each perturbed variable. + """ + if FileToRead is None: + fp = open('perturbation_cases.dat','r') + else: + fp = open(FileToRead,'r') + + varList = fp.readline().strip().split(" ") + perturbedVariables = [k for k in varList if k!="#" and k!=""] + fp.readline() + DictOfCases = {} + for line in fp.readlines(): + caseData = line.strip().split(" ") + caseName = caseData[0] + caseValues = [float(k) for k in caseData if k!=caseName and k!=""] + DictOfCases[caseName] = dict(zip(perturbedVariables,caseValues)) + #DictOfCases[caseName] = [float(k) for k in caseData \ + # if k!=caseName and k!=""] + fp.close() + return perturbedVariables, DictOfCases + + +def write_case_config(caseDict): + """Short function that writes a file summarising the values of + an input dictionary. + """ + fout = open('nominal_case.config','w') + for k in range(len(caseDict)): + fout.write('{0}'.format(caseDict.keys()[k]+': ')) + fout.write('{0}'.format(caseDict.values()[k])) + fout.write('\n') + fout.close() + + +# The below function was originally used in nenzfr_sensitivity. Parts of +# the code have now been incorporated into the function "set_case_running" +# (see above). LukeD 05-05-2020 +# +#def perturb_CoreRadiusFraction(var, perturbedVariables,\ +# DictOfCases, levels): +# """ +# Perturbation of the CoreRadiusFraction may be completed +# as a post-processing step using the nominal condition +# flow solution. We don't need to compute separate solutions. +# +# This function copies the nominal condition solution to a +# new case file and then runs nenzfr with the --just-stats +# option and the perturbed CoreRadiusFraction value. +# """ +# +# for kk in range(levels): +# if kk != 0: +# caseString = 'case'+"{0:02}".format(\ +# perturbedVariables.index(var))+\ +# "{0:01}".format(kk) +# # Only if required do we proceed with copying the +# # nominal case data and calling 'nenzfr.py' +# if not os.path.exists(caseString): +# print "Perturbing "+var +# #command_text = 'rm -r '+caseString +# #run_command(command_text) +# +# # Copy nominal case data into a new case +# # directory +# command_text = 'cp -r case000 '+caseString +# run_command(command_text) +# # Change into new directory and call nenzfr --just-stats +# os.chdir(caseString) +# command_text = \ +# 'nenzfr.py --just-stats --CoreRadiusFraction='+\ +# str(DictOfCases[caseString][perturbedVariables.index(var)]) +# run_command(command_text) +# os.chdir('../') + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/nenzfr/sensitivity.py Wed Jun 03 20:53:51 2020 +1000 @@ -0,0 +1,384 @@ +#!/usr/bin/env python +""" sensitivity.py + +This script calculates the sensitivity and uncertainty in each +output property due to the various input parameters using a set +of calculations generated by "perturb.py". + +The calculation method follows that outlined in the text book: + Coleman, HW and Steele, WG (1999). Experimentation and + Uncertainty Analysis for Engineers. 2nd edition. New York, + John Wiley & Sons, Inc. DOI: 10.2514/2.3773 + +Note that this script DOES NOT account for correlated uncertainties; +that is, it assumes the input variable uncertainties are completely +independent of one another. + +The user must supply a function called "read_outfile.py" which reads +the outfile of the users analysis code and returns a dictionary of +results and list of output variables (to be analysed). + +.. Author: Luke Doherty (luke.doherty@eng.ox.ac.uk) + Oxford Thermofluids Institute + The University of Oxford +""" + +VERSION_STRING = "05-May-2020" + +import shlex, string +import sys, os, copy +E3BIN = os.path.expandvars("$HOME/e3bin") +sys.path.append(E3BIN) # installation directory +sys.path.append("") # so that we can find user's scripts in current directory +import optparse +import numpy as np #import array +np.seterr(divide='ignore',invalid='ignore') +from perturb_utils import read_case_summary +from sensitivity_input_utils import sensitivity_input_checker +from sensitivity_utils import get_values, write_sensitivity_summary,\ + write_uncertainty_summary +from read_outfile import read_outfile + +#from nenzfr_utils import run_command, quote, read_case_summary, \ +# read_nenzfr_outfile, read_estcj_outfile +#from nenzfr_input_utils import input_checker, nenzfr_perturbed_input_checker, nenzfr_sensitivity_input_checker + +#--------------------------------------------------------------- +def run_sensitivity(cfg): + """ + Function that accepts a config dictionary and runs a + sensitivity analysis + """ + # + cfg['bad_input'] = False + # + # First check our input + cfg = sensitivity_input_checker(cfg) + # bail out here if there is an issue + if cfg['bad_input']: + return -2 + + # Read the sensitivity_case_summary file to get the perturbed + # variables and their various values + perturbedVariables, dictOfCases = read_case_summary() + #dontNeed, dictOfCases = read_case_summary() + #perturbedVariables = copy.deepcopy(cfg['inputVariables']) + + #print perturbedVariables + #print dictOfCases + #print + #print cfg + + # Make a copy of list of variables we want to analyse + exitVar = copy.deepcopy(cfg['outputVariables']) + + # Define the name of the nominal case and load the exit plane data + nominal = 'case000' + nominalData, allExitVar = \ + read_outfile('./'+nominal+'/'+cfg['resultsFile']) + + #nominalData, exitVar = read_nenzfr_outfile('./'+nominal+'/'+\ + # cfg['jobName']+'-exit.stats') + # Load the nozzle supply data + #nominalSupply = read_estcj_outfile('./'+nominal+'/'+cfg['jobName']+'-estcj.dat') + # Now add the relevant supply data (T, h) to the nominalData dictionary + #nominalData['supply_rho'] = nominalSupply['rho'] + #nominalData['supply_T'] = nominalSupply['T'] + #nominalData['supply_h'] = nominalSupply['h'] + # Add the supply variables to the exitVar list + #exitVar.insert(0,'supply_rho') + #exitVar.insert(1,'supply_T') + #exitVar.insert(2,'supply_h') + # Add extra variables of interest (q, Re_u, m_dot) + #nominalData, exitVar = add_extra_variables(nominalData, exitVar) + + #nominalValues = get_values(nominalData, exitVar) + #print 'nominalValues: ',nominalValues + + + # Extract the input uncertainties for easier use. We must use the + # relative values + inputUncertainties = copy.deepcopy(cfg['inputVariablesUncertainty_rel']) + + + # Loop through each of the perturbed variables + sensitivity_rel = {} + #sensitivity_rel2 = {} + sensitivity_abs = {} + #sensitivity_abs2 = {} + uncertainty = {} + #uncertainty2 = {} + + for k in range(len(perturbedVariables)): + var = perturbedVariables[k] + print var + #if var == 'CoreRadiusFraction': + # perturb_CoreRadiusFraction(var, perturbedVariables,\ + # dictOfCases, cfg['levels']) + + # Define the name of the relevant perturbed cases and load the + # associated data + high = 'case'+"{0:02}".format(k)+'1' + highData, dontNeed = read_outfile('./'+high+'/'+cfg['resultsFile']) + + #highData, dontNeed = read_nenzfr_outfile('./'+high+'/'+\ + # cfg['jobName']+'-exit.stats') + #highSupply = read_estcj_outfile('./'+high+'/'+cfg['jobName']+'-estcj.dat') + #highData['supply_rho'] = highSupply['rho'] + #highData['supply_T'] = highSupply['T'] + #highData['supply_h'] = highSupply['h'] + #highData, dontNeed = add_extra_variables(highData, []) + + low = 'case'+"{0:02}".format(k)+'2' + lowData, dontNeed = read_outfile('./'+low+'/'+cfg['resultsFile']) + + #lowData, dontNeed = read_nenzfr_outfile('./'+low+'/'+\ + # cfg['jobName']+'-exit.stats') + #lowSupply = read_estcj_outfile('./'+low+'/'+cfg['jobName']+'-estcj.dat') + #lowData['supply_rho'] = lowSupply['rho'] + #lowData['supply_T'] = lowSupply['T'] + #lowData['supply_h'] = lowSupply['h'] + #lowData, dontNeed = add_extra_variables(lowData, []) + + print low, nominal, high + + # Values of the freestream properties at the perturbed conditions + #highValues = get_values(highData,exitVar) + #lowValues = get_values(lowData,exitVar) + #print 'highValues: ',highValues + #print 'lowValues: ',lowValues + #print 'highData: ',highData + #print 'lowData: ',lowData + #print 'nominalData: ',nominalData + #print dictOfCases[high] + #print perturbedVariables + #print perturbedVariables.index(var) + #print dictOfCases[high][var] + + # Values of the perturbed input values + highX = dictOfCases[high][var] + lowX = dictOfCases[low][var] + nominalX = dictOfCases[nominal][var] + #print 'highX: ',highX + #print 'lowX: ',lowX + #print 'nominalX: ',nominalX + + if cfg['levels'] == 3: + # As the perturbations may not be centered on the nominal + # condition we caculate the gradient by taking a weighted + # average of the forward and backward derivatives. The + # weightings are such that the truncation error associated + # with this gradient estimate is O(3) or higher (i.e. the + # weightings are such that the second order terms in the Taylor + # series expansion cancel. Thanks to D.Petty for this theory.) + highWeighting = (nominalX - lowX)/(highX - lowX) + lowWeighting = (highX - nominalX)/(highX - lowX) + + # Calculate the aboslute sensitivities + #sensitivity_abs[var] = ( highWeighting*(np.array(highValues)-\ + # np.array(nominalValues))/\ + # (highX - nominalX) + \ + # lowWeighting*(np.array(nominalValues)-\ + # np.array(lowValues))/\ + # (nominalX - lowX) ) + sensitivity_abs[var] = {k: ( highWeighting*(highData[k]-\ + nominalData[k]) /\ + (highX - nominalX) + \ + lowWeighting*(nominalData[k]-\ + lowData[k]) /\ + (nominalX - lowX) ) \ + for k in exitVar} + #print + #print 'exitVar: ',exitVar + #print + #print 'sens_abs[var]: ',sensitivity_abs[var] + + # Calculate the relative sensitivities + #sensitivity_rel[var] = sensitivity_abs[var]*\ + # nominalX/np.array(nominalValues) + sensitivity_rel[var] = {k: sensitivity_abs[var][k]*\ + nominalX/nominalData[k] \ + for k in exitVar} + + #print 'sens_rel[var]: ',sensitivity_rel[var] + + elif cfg['levels'] == 5: + # For 5 levels per variable we have additional cases + # that need to be loaded. Again we do not assume that + # the levels are equally spaced around the nominal. + # The weightings are such that the truncation error + # associated with this estimate is O(4) or higher. + tooHigh = 'case'+"{0:02}".format(k)+'3' + tooHighData, dontNeed = \ + read_outfile('./'+tooHigh+'/'+cfg['resultsFile']) + #tooHighData,dontNeed = \ + # read_nenzfr_outfile('./'+tooHigh+'/'+cfg['jobName']+'-exit.stats') + #tooHighSupply = read_estcj_outfile('./'+tooHigh+'/'+cfg['jobName']+'-estcj.dat') + #tooHighData['supply_rho'] = tooHighSupply['rho'] + #tooHighData['supply_T'] = tooHighSupply['T'] + #tooHighData['supply_h'] = tooHighSupply['h'] + #tooHighData, dontNeed = add_extra_variables(tooHighData, []) + + tooLow = 'case'+"{0:02}".format(k)+'4' + tooLowData, dontNeed = \ + read_outfile('./'+tooLow+'/'+cfg['resultsFile']) + + #tooLowData,dontNeed = \ + # read_nenzfr_outfile('./'+tooLow+'/'+cfg['jobName']+'-exit.stats') + #tooLowSupply = read_estcj_outfile('./'+tooLow+'/'+cfg['jobName']+'-estcj.dat') + #tooLowData['supply_rho'] = tooLowSupply['rho'] + #tooLowData['supply_T'] = tooLowSupply['T'] + #tooLowData['supply_h'] = tooLowSupply['h'] + #tooLowData, dontNeed = add_extra_variables(tooLowData, []) + + #print tooHigh, high, nominal, low, tooLow + + # Values of the freestream properties at the perturbed conditions + #tooHighValues = get_values(tooHighData,exitVar) + #tooLowValues = get_values(tooLowData,exitVar) + + # Values of the perturbed input values + tooHighX = dictOfCases[tooHigh][var] + tooLowX = dictOfCases[tooLow][var] + tooHighDeltaX = tooHighX - nominalX + highDeltaX = highX - nominalX + tooLowDeltaX = tooLowX - nominalX + lowDeltaX = lowX - nominalX + weighting = (tooHighX - tooLowX)/(highX - lowX) + + denom = 1/tooHighDeltaX - 1/tooLowDeltaX -\ + (1/highDeltaX - 1/lowDeltaX)*weighting + + #numer = np.array(tooHighValues)/tooHighDeltaX**2 -\ + # np.array(tooLowValues)/tooLowDeltaX**2 -\ + # ( np.array(highValues)/highDeltaX**2 -\ + # np.array(lowValues)/lowDeltaX**2 )*weighting -\ + # np.array(nominalValues)*\ + # ( 1/tooHighDeltaX**2 - 1/tooLowDeltaX**2 - \ + # ( 1/highDeltaX**2 - 1/lowDeltaX**2 )*weighting ) + + numer = {k: tooHighData[k]/tooHighDeltaX**2 -\ + tooLowData[k]/tooLowDeltaX**2 -\ + ( highData[k]/highDeltaX**2 -\ + lowData[k]/lowDeltaX**2 )*weighting -\ + nominalData[k]*\ + ( 1/tooHighDeltaX**2 - 1/tooLowDeltaX**2 - \ + ( 1/highDeltaX**2 - 1/lowDeltaX**2 )*weighting ) \ + for k in exitVar} + + #numer2values = get_values(numer2,exitVar) + #numer2check = numer2values - numer + #print + #print 'denom: ',denom + #print + #print 'exitVar: ',exitVar + #print + #print 'numer: ',numer + #print + #print 'numer2: ',numer2 + #print + #print 'numer2check: ',numer2check + #print + #print + + # Calculate the absolute sensitivities + #sensitivity_abs[var] = numer/denom + sensitivity_abs[var] = {k: numer[k]/denom for k in exitVar} + + #sens_abs2values = get_values(sensitivity_abs2[var],exitVar) + #sens_abs2check = sens_abs2values - sensitivity_abs[var] + + # Calculate relative sensitivities + #sensitivity_rel[var] = sensitivity_abs[var]*nominalX/\ + # np.array(nominalValues) + sensitivity_rel[var] = {k: sensitivity_abs[var][k]*\ + nominalX/nominalData[k] \ + for k in exitVar} + + #sens_rel2values = get_values(sensitivity_rel2[var],exitVar) + #sens_rel2check = sens_rel2values - sensitivity_rel[var] + # + #print 'sens_abs[var]: ',sensitivity_abs[var] + #print + #print 'sens_abs2[var]: ',sensitivity_abs2[var] + #print + #print 'sens_abs2check[var]: ',sens_abs2check + #print + #print 'sens_rel2check[var]: ',sens_rel2check + #print + + # Now calculate the uncertainty in each exit flow variable + # due to the uncertainty in the current (perturbed) + # input variable + #uncertainty[var] = sensitivity_rel[var]*inputUncertainties[var] + + uncertainty[var] = {k: sensitivity_rel[var][k]*\ + inputUncertainties[var] for k in exitVar} + + #uncert2values = get_values(uncertainty2[var],exitVar) + #uncert2check = uncert2values - uncertainty[var] + #print + #print 'exitVar: ',exitVar + #print + #print 'uncertainty[var]: ',uncertainty[var] + #print + #print 'uncertainty2[var]: ',uncertainty2[var] + #print + #print 'inputUncertainties[var]: ',inputUncertainties[var] + #print + #print 'uncert2check: ',uncert2check + #print + + + # Write out a file of the sensitivities + write_sensitivity_summary(sensitivity_rel, perturbedVariables,\ + exitVar, 'relative') + write_sensitivity_summary(sensitivity_abs, perturbedVariables,\ + exitVar, 'absolute') + + # Write out a file of the uncertainties + write_uncertainty_summary(uncertainty, perturbedVariables,\ + exitVar, inputUncertainties) + + return + + + +def main(): + """ + Examine the command-line options and then calculate the sensitivties + and uncertainties of each property based on a set of results generated + by "perturb.py". + """ + op = optparse.OptionParser(version=VERSION_STRING) + op.add_option('-c', '--config_file', dest='config_file', + help=("filename for the config file")) + opt, args = op.parse_args() + config_file = opt.config_file + # + cfg = {} + # + if not cfg: #if the configuration dictionary has not been filled up already, load it from a file + try: #from Rowan's onedval program + execfile(config_file, globals(), cfg) + except IOError as e: + print "Error {0}".format(str(e)) + print "There was a problem reading the config file: '{0}'".format(config_file) + print "Check that it conforms to Python syntax." + print "Bailing out!" + sys.exit(1) + # + run_sensitivity(cfg) + # + return + +#--------------------------------------------------------------- + +if __name__ == '__main__': + if len(sys.argv) <= 1: + print "sensitivity:\n Calculate Sensitivity of Shock Tunnel Test Flow Conditions for a varying inputs" + print " Version:", VERSION_STRING + print " To get some useful hints, invoke the program with option --help." + sys.exit(0) + return_flag = main() + sys.exit(return_flag)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/nenzfr/sensitivity_input_utils.py Wed Jun 03 20:53:51 2020 +1000 @@ -0,0 +1,114 @@ +""" +sensitivity_input_utils.py -- Function to check the sensitivity input + dictionary that is pulled in from a config file. Default values + are added where appropriate. + +.. Author: Luke Doherty (luke.doherty@eng.ox.ac.uk) + Oxford Thermofluids Institute + The University of Oxford + + Version: 04-May-2020 +""" + +import sys, os, copy +E3BIN = os.path.expandvars("$HOME/e3bin") +sys.path.append(E3BIN) # installation directory +sys.path.append("") # so that we can find user's scripts in current directory +from read_outfile import read_outfile +from perturb_utils import read_case_summary + +#-------------------------------------------------------------------------- +def sensitivity_input_checker(cfg): + """Takes the config dictionary and checks that all the necessary + inputs are in there. Will bail out if important variables are missing, + and set default values for those that are not crucial. + + Returns the config dictionary after it has been checked over and will + tell sensitivity to bail out if it finds an issue. + + """ + + print "Checking sensitivity inputs." + + # Make empty input uncertainties dictionary that we will populate + #cfg['inputUncertainties'] = {} + + if 'levels' not in cfg: + cfg['levels'] = 3 + print "The number of perturbation levels to use is not specified." + print " Setting it to default value of {0}".format(cfg['levels']) + + if 'outputVariables' not in cfg: + print "You have not specified a list of which variables are to be analysed." + print " Bailing out." + cfg['bad_input'] = True + + if 'inputVariables' not in cfg: + print "You have not specified a list of the input variables (that have been perturbed." + print " Bailing out." + cfg['bad_input'] = True + + if 'inputVariablesUncertainty' not in cfg: + print "You have not specified a dictionary of the input variables uncertainties." + print " Bailing out." + cfg['bad_input'] = True + + if 'typeOfUncertainty' not in cfg: + print "You have not specified the type of uncertainty (absolute or relative)." + print " Bailing out." + cfg['bad_input'] = True + + # Check that we have uncertainties for each input variable + for variable in cfg['inputVariables']: + #print variable + #print cfg['inputVariablesUncertainty'].keys() + + if variable not in cfg['inputVariablesUncertainty'].keys(): + print "You have not specified an uncertainty for {0}".format(variable) + print " Bailing out." + cfg['bad_input'] = True + + # Read the sensitivity_case_summary file to get the perturbed + # variables and their various values + perturbedVariables, dictOfCases = read_case_summary() + + # Define the name of the nominal case and load the exit plane data + nominal = 'case000' + nominalData, dontNeed = read_outfile('./'+nominal+'/'+cfg['resultsFile']) + + # Check for discrepancies between the perturbedVariables list and the + # inputVariables list + for var in perturbedVariables: + if var not in cfg['inputVariables']: + print "'{0}' was perturbed but does not appear in the 'inputVariables' list.".format(var) + # Bailing out could be made unnecessary but it would require + # signifiant changes to how the main sensitivity function + # works since the case number is specific to the variable + # position in the inputVariables list. + print " Bailing out." + cfg['bad_input'] = True + + # Clean up the input uncertainty. Not sure if this is better here + # or in the main function + if cfg['typeOfUncertainty'] in ['absolute']: + cfg['inputVariablesUncertainty_abs'] = copy.deepcopy(cfg['inputVariablesUncertainty']) + cfg['inputVariablesUncertainty_rel'] = {} + for var in cfg['inputVariables']: + cfg['inputVariablesUncertainty_rel'][var] = \ + cfg['inputVariablesUncertainty_abs'][var]/\ + dictOfCases['case000'][var] + else: + cfg['inputVariablesUncertainty_rel'] = copy.deepcopy(cfg['inputVariablesUncertainty']) + cfg['inputVariablesUncertainty_abs'] = {} + for var in cfg['inputVariables']: + cfg['inputVariablesUncertainty_abs'][var] = \ + cfg['inputVariablesUncertainty_rel'][var]*\ + dictOfCases['case000'][var] + cfg.pop('inputVariablesUncertainty') + cfg.pop('typeOfUncertainty') + + + print "Done checking sensitivity inputs." + + return cfg +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/nenzfr/sensitivity_utils.py Wed Jun 03 20:53:51 2020 +1000 @@ -0,0 +1,174 @@ +""" +sensitivity_utils.py -- Small untility functions needed by the + sensitivity programs. + +.. Author: Luke Doherty (luke.doherty@eng.ox.ac.uk) + Oxford Thermofluids Institute + The University of Oxford + + Version: 04-May-2020 +""" +#-------------------------------------------------------------------------- +def get_values(dict, propertyList): + """ + Adapted from Peter Jacob's function "get_fractions" which + may be found within "cea2_gas.py". + """ + valueList = [] + #print propertyList + for s in propertyList: + #print s + if s in dict.keys(): + #print s + #print dict[s] + valueList.append(dict[s]) + else: + valueList.append(0.0) + print "WARNING: "+s+"was not found in the current case dictionary." + return valueList + + +def write_sensitivity_summary(sensitivity, perturbedVariables, \ + exitVar, type): + """ + Write out a file summarising the sensitivity of each exit flow + parameter to each of the input parameters. + """ + + titleFormatDict = {k : '{0:{fill}>'+"{0}".format(max(11,len(k))+2)+"}" \ + for k in perturbedVariables} + #print 'titleFormatDict: ',titleFormatDict + + formatDict = {k : '{0:>'+"{0}".format(max(11,len(k))+2)+".5g}" \ + for k in perturbedVariables} + #print 'formatDict: ',formatDict + + # Old nenzfr-specific header formatting dictionaries + #titleFormatDict = {'p1':'{0:{fill}>13}', 'T1':'{0:{fill}>13}', + # 'Vs':'{0:{fill}>13}', 'pe':'{0:{fill}>13}', + # 'Tw':'{0:{fill}>13}', 'BLTrans':'{0:{fill}>13}', + # 'TurbVisRatio':'{0:{fill}>14}', + # 'TurbInten':'{0:{fill}>13}', + # 'CoreRadiusFraction':'{0:{fill}>20}'} + #formatDict = {'p1':'{0:13.5g}', 'T1':'{0:>13.5g}', + # 'Vs':'{0:>13.5g}', 'pe':'{0:>13.5g}', + # 'Tw':'{0:>13.5g}', 'BLTrans':'{0:>13.5g}', + # 'TurbVisRatio':'{0:>14.5g}', + # 'TurbInten':'{0:>13.5g}', + # 'CoreRadiusFraction':'{0:>20.5g}'} + + # Open the file + if type in ['relative']: + #fout = open('sensitivities_rel.dat','w') + fout = open('sensitivities_rel.dat','w') + elif type in ['absolute']: + fout = open('sensitivities_abs.dat','w') + + + # Write header information + #fout.write('{0:}\n'.format(type+' sensitivities')) + fout.write('{0:}\n'.format(type+' sensitivities')) + #fout.write('{0:>13}'.format('variable')) + fout.write('{0:>13}'.format('variable')) + # + for k in perturbedVariables: + #fout.write(titleFormatDict[k].format(k,fill='')) + fout.write(titleFormatDict[k].format(k,fill='')) + fout.write('\n') + # + for k in perturbedVariables: + #fout.write(titleFormatDict[k].format('-',fill='-')) + fout.write(titleFormatDict[k].format('-',fill='-')) + fout.write('{0:->13}'.format('-')) + fout.write('\n') + + # Now write out all the data + for k in exitVar: + #fout.write('{0:>13}'.format(k)) + fout.write('{0:>13}'.format(k)) + for kk in perturbedVariables: + #X_kk = sensitivity[kk][exitVar.index(k)] + X_kk = sensitivity[kk][k] + + fout.write(formatDict[kk].format(X_kk)) + # + fout.write('\n') + # + fout.close() + + if type in ['relative']: + print 'File "sensitivities_rel.dat" written' + elif type in ['absolute']: + print 'File "sensitivities_abs.dat" written' + + return + +def write_uncertainty_summary(uncertainty, perturbedVariables, \ + exitVar, inputUncertainties): + """ + Write out a file summarising the sensitivity of each exit flow + parameter to each of the input parameters. + """ + + titleFormatDict = {k : '{0:{fill}>'+"{0}".format(max(8,len(k))+2)+"}" \ + for k in perturbedVariables} + #print 'titleFormatDict: ',titleFormatDict + + formatDict = {k : '{0:>'+"{0}".format(max(8,len(k))+2)+".2%}" \ + for k in perturbedVariables} + #print 'formatDict: ',formatDict + + # Old nenzfr-specific header formatting dictionaries + #titleFormatDict = {'p1':'{0:{fill}>10}', 'T1':'{0:{fill}>10}', + # 'Vs':'{0:{fill}>10}', 'pe':'{0:{fill}>10}', + # 'Tw':'{0:{fill}>10}', 'BLTrans':'{0:{fill}>10}', + # 'TurbVisRatio':'{0:{fill}>14}', + # 'TurbInten':'{0:{fill}>11}', + # 'CoreRadiusFraction':'{0:{fill}>20}'} + #formatDict = {'p1':'{0:10.2%}', 'T1':'{0:>10.2%}', + # 'Vs':'{0:>10.2%}', 'pe':'{0:>10.2%}', + # 'Tw':'{0:>10.2%}', 'BLTrans':'{0:>10.2%}', + # 'TurbVisRatio':'{0:>14.2%}', + # 'TurbInten':'{0:>11.2%}', + # 'CoreRadiusFraction':'{0:>20.2%}'} + + fout = open('uncertainties.dat','w') + + # Write header information + fout.write('{0:>19}'.format('variable')) + for k in perturbedVariables: + fout.write(titleFormatDict[k].format(k,fill='')) + fout.write('{0:>10}'.format('total')) + fout.write('\n') + + # Write out the uncertainty for each perturbed variable + fout.write('{0:>19}'.format('(input uncertainty)')) + for k in perturbedVariables: + fout.write(formatDict[k].format(inputUncertainties[k])) + fout.write('{0:>10}'.format('')) + fout.write('\n') + for k in perturbedVariables: + fout.write(titleFormatDict[k].format('-',fill='-')) + fout.write('{0:->29}'.format('-')) + fout.write('\n') + + # Now write out all the data. We also calculate and write out the + # total uncertainty in each exit flow variable due to the contributions + # from all input values. + for k in exitVar: + fout.write('{0:>19}'.format(k)) + X_total = 0.0 + for kk in perturbedVariables: + #X_kk = uncertainty[kk][exitVar.index(k)] + X_kk = uncertainty[kk][k] + fout.write(formatDict[kk].format(X_kk)) + X_total += X_kk**2 # Calculate total + fout.write('{0:10.2%}'.format(X_total**0.5)) + fout.write('\n') + + fout.close() + + print 'File "uncertainties.dat" written' + return + +