"""basic syntax of the parameter file is:: # simple parameter file [driver] nsteps = 100 ; comment max_time = 0.25 [riemann] tol = 1.e-10 max_iter = 10 [io] basename = myfile_The recommended way to use this is for the code to have a master listof parameters and their defaults (e.g. _defaults), and then the usercan override these defaults at runtime through an inputs file. Thesetwo files have the same format.The calling sequence would then be:: rp = RuntimeParameters() rp.load_params("_defaults") rp.load_params("inputs")The parser will determine what datatype the parameter is (string,integer, float), and store it in a RuntimeParameters object. If aparameter that already exists is encountered a second time (e.g.,there is a default value in _defaults and the user specifies a newvalue in inputs), then the second instance replaces the first.Runtime parameters can then be accessed via any module through theget_param method:: tol = rp.get_param('riemann.tol')If the optional flag no_new=1 is set, then the load_params functionwill not define any new parameters, but only overwrite existing ones.This is useful for reading in an inputs file that overrides previouslyread default values."""importosimportreimporttextwrapfrompathlibimportPathfrompyro.utilimportmsg# some utility functions to automagically determine what the data# types are
[docs]defis_int(string):""" is the given string an integer? """try:int(string)exceptValueError:returnFalsereturnTrue
[docs]defis_float(string):""" is the given string a float? """try:float(string)exceptValueError:returnFalsereturnTrue
[docs]classRuntimeParameters:def__init__(self):""" Initialize a collection of runtime parameters. This class holds a dictionary of the parameters, their comments, and keeps track of which parameters were actually used. """# keep track of the parameters and their commentsself.params={}self.param_comments={}# for debugging -- keep track of which parameters were# actually looked- upself.used_params=[]
[docs]defload_params(self,pfile,*,no_new=False):""" Reads line from file and makes dictionary pairs from the data to store. Parameters ---------- file : str The name of the file to parse no_new : int, optional If no_new = 1, then we don't add any new parameters to the dictionary of runtime parameters, but instead just override the values of existing ones. """# check to see whether the file existsifnotos.path.isfile(pfile):pfile=str(Path(__file__).resolve().parents[1]/pfile)try:f=open(pfile)exceptOSError:msg.fail(f"ERROR: parameter file does not exist: {pfile}")# we could use the ConfigParser, but we actually want to# have our configuration files be self-documenting, of the# format key = value ; commentsec=re.compile(r'^\[(.*)\]')eq=re.compile(r'^([^=#]+)=([^;]+);{0,1}(.*)')forlineinf.readlines():ifsec.search(line):_,section,_=sec.split(line)section=section.strip().lower()elifeq.search(line):_,item,value,comment,_=eq.split(line)item=item.strip().lower()# define the keykey=section+"."+item# if we have no_new = 1, then we only want to override existing# key/valuesifno_new:ifkeynotinself.params:msg.warning("warning, key: %s not defined"%(key))continueself.params[key]=_get_val(value)# if the comment already exists (i.e. from reading in# _defaults) and we are just resetting the value of# the parameter (i.e. from reading in inputs), then# we don't want to destroy the commentifcomment.strip()=="":try:comment=self.param_comments[key]exceptKeyError:comment=""self.param_comments[key]=comment.strip()
[docs]defget_param(self,key):""" returns the value of the runtime parameter corresponding to the input key """ifnotself.params:msg.warning("WARNING: runtime parameters not yet initialized")self.load_params("_defaults")# debuggingifkeynotinself.used_params:self.used_params.append(key)ifkeyinself.params:returnself.params[key]raiseKeyError(f"ERROR: runtime parameter {key} not found")
[docs]defset_param(self,key,value,*,no_new=True):""" manually set one of the existing runtime parameters """ifnotself.params:msg.warning("WARNING: runtime parameters not yet initialized")self.load_params("_defaults")ifno_newandkeyinself.params:self.params[key]=valuereturnifnotno_new:self.params[key]=valueself.param_comments[key]=""returnraiseKeyError(f"ERROR: runtime parameter {key} not found")
[docs]defprint_unused_params(self):""" Print out the list of parameters that were defined by never used """forkeyinself.params:ifkeynotinself.used_params:msg.warning("parameter %s never used"%(key))
[docs]defprint_all_params(self):""" Print out all runtime parameters and their values """forkeyinsorted(self.params.keys()):print(key,"=",self.params[key])print(" ")
[docs]defwrite_params(self,f):""" Write the runtime parameters to an HDF5 file. Here, f is the h5py file object """grp=f.create_group("runtime parameters")keys=self.params.keys()forkeyinsorted(keys):grp.attrs[key]=self.params[key]
[docs]defprint_paramfile(self):""" Create a file, inputs.auto, that has the structure of a pyro inputs file, with all known parameters and values """all_keys=list(self.params.keys())try:f=open('inputs.auto','w')exceptOSError:msg.fail("ERROR: unable to open inputs.auto")f.write('# automagically generated parameter file\n')# find all the sectionssecs={qfor(q,_)in[k.split(".")forkinall_keys]}forsecinsorted(secs):keys=[qforqinall_keysifq.startswith(f"{sec}.")]f.write(f"\n[{sec}]\n")forkeyinkeys:_,option=key.split('.')value=self.params[key]ifself.param_comments[key]!='':f.write(f"{option} = {value} ; {self.param_comments[key]}\n")else:f.write(f"{option} = {value}\n")f.close()
[docs]defprint_sphinx_tables(self,outfile="params-sphinx.inc"):"""Output Sphinx-formatted tables for inclusion in the documentation. The table columns will be: param, default, description. """all_keys=list(self.params.keys())try:f=open(outfile,'w')exceptOSError:msg.fail("ERROR: unable to open inputs.auto")# find all the sectionssecs={qfor(q,_)in[k.split(".")forkinall_keys]}heading=" +="+36*"="+"=+="+16*"="+"=+="+50*"="+"=+"+"\n"separator=" +-"+36*"-"+"-+-"+16*"-"+"-+-"+50*"-"+"-+"+"\n"entry=" | {:36} | {:16} | {:50} |\n"forsecinsorted(secs):keys=[qforqinall_keysifq.startswith(f"{sec}.")]head=f"* section: ``[{sec.strip()}]``"f.write(f"{head}\n\n")#f.write(len(head)*"^"+"\n\n")f.write(separator)f.write(entry.format("option","value","description"))f.write(heading)forkeyinkeys:_,option=key.split('.')descr=textwrap.wrap(self.param_comments[key].strip(),50)iflen(descr)==0:descr=[" "]f.write(entry.format("``"+option+"``",f"``{str(self.params[key]).strip()}``",descr[0]))iflen(descr)>1:forlineindescr[1:]:f.write(entry.format("","",line))f.write(separator)f.write("\n")f.close()