"""Code related to Runtime Parameter Info."""
import string
import re
import sys
import UserDict
import os.path
# relative imports needed
from . import setup_globals
from .setup_globals import gvars, SetupError
from .lazy_file import LazyFile
###############################################################
[docs]class RPInfo(UserDict.UserDict):
"""Class which keeps track of run time parameters and related info
To each runtime parameter we keep track of the following info:
"NAME" of the parameter
"TYPE" - REAL, INTEGER, BOOLEAN, STRING
"VALUE" - Initial value of parameter
"CONST" - Boolean saying if this value is constant
"COMMENT" - Info regarding parameter
"RANGE" - [ list of range specifications ] range spec = {"min":min_val,"max":max_val} or "STRING"
self is a dictionary mapping "LOCATION","NAME" to other info
self.locations is a dictionary mapping "NAME" to list of locations defining it
If warn is non-empty duplicate names are considered WARNING, else duplicate
(NAME,LOCATION) is WARNING else it is OK
If it is a warning then RP not added to database
"""
def __init__(self,warn=1):
UserDict.UserDict.__init__(self)
self.typemap = {'REAL': 'real',
'INTEGER': 'integer',
'STRING': 'character(len=MAX_STRING_LENGTH)',
'BOOLEAN':'logical',
'DOC': ""}
self.warn = warn
self.locations = {}
self.rangeToregexp = re.compile(r"to|TO|[.][.][.]")
self.numregexp = re.compile(r"\s*(?P<min>\S*)\s*(?:to|TO|[.][.][.])\s*(?P<max>\S*)\s*")
self.numre= re.compile(r"^[-+]?[0-9]*([.]([0-9]*([eE][-+]?[0-9]*)?)?)?$")
def parseRange(self,range,typ,name):
def checkNum(what):
if not what: return 1
if what=="TINY": return 1
if what=="-TINY": return 1
# strip trailing +,-
if what[-1] in ["+","-"]: what = what[-1]
if self.numre.match(what):
return 1
else: return None
rv = []
if typ not in ["REAL","INTEGER","STRING"]:
return rv
if not range:
return rv
for rangespec in range.split(","):
rangespec = rangespec.strip()
if not rangespec: continue
if (self.rangeToregexp.search(rangespec) >= 0) and (typ in ["REAL","INTEGER"]): # contains "to" as a substring
spec = {"min":None,"max":None}
m = self.numregexp.match(rangespec)
spec.update(m.groupdict())
rv.append(spec)
else:
if rangespec[0] == rangespec[-1] and rangespec[0] in ["'",'"']:
rangespec = rangespec[1:-1]
if typ in ["REAL","INTEGER"]: # checking for membership in degenerate interval
rv.append( {"min":rangespec,"max":rangespec})
elif typ in ["STRING"]:
rv.append(rangespec)
# check if given numbers are valid
if typ in ["REAL","INTEGER"]:
for rs in rv: # for each range specification
if not checkNum(rs["min"]): # min is bad
raise SetupError("minimum value '%s' is illformed in parameter '%s'" % (rs["min"],name))
if not checkNum(rs["max"]): # min is bad
raise SetupError("maximum value '%s' is illformed in parameter '%s'" % (rs["max"],name))
return rv
def addRP(self,name,type="",value="",const=None,location="",comment="",range=None):
if self.warn:
if self.locations.has_key(name):
loc2 = self.locations[name][0]
val2 = self[(loc2,name)]["VALUE"]
typ2 = self[(loc2,name)]["TYPE"]
if (val2 == value) and (typ2 == type):
# already present ignore this call
if (self[(loc2,name)]["RANGE"] == self.parseRange(range,type,name)): return
gvars.out.push()
quit = 1 # quit this call
sP = setup_globals.SIMULATION_PREFIX
if sP.endswith(os.sep): sP = sP[:-1]
# check if either of them is in SimlulationUnit. If so that overrides (exclusive OR)
if (loc2.startswith(sP) and not location.startswith(sP)) or \
(not loc2.startswith(sP) and location.startswith(sP)):
gvars.out.put('\nINFO: Parameter %s defined in both\n%s (default %s) and \n%s (default %s)'
%(name, loc2, val2, location, value),setup_globals.IMPINFO)
gvars.out.put("Simulation instance overrides; removing other instance.",setup_globals.IMPINFO)
if location.startswith(sP): # remove existing details
del self[(loc2,name)]
self.locations[name].remove(loc2)
quit = 0
elif loc2.startswith(location) or location.startswith(loc2):
gvars.out.put('\nINFO: Parameter %s defined in both\n%s (%s default %s) and \n%s (%s default %s)'
%(name, loc2, typ2, val2, location, type, value),setup_globals.INFO)
gvars.out.put("Longer path wins, overriding the less specific instance.",setup_globals.INFO)
if location.startswith(loc2): # remove existing details
del self[(loc2,name)]
self.locations[name].remove(loc2)
quit = 0
elif (
(self[(loc2,name)]["RANGE"]==[] and range) or
(self[(loc2,name)]["RANGE"]!=[] and not range)):
gvars.out.put('\nINFO: Parameter %s defined in both\n%s (%s default %s) and \n%s (%s default %s)'
%(name, loc2, typ2, val2, location, type, value),setup_globals.IMPINFO)
gvars.out.put("Specification with range wins, overriding the less specific instance.",setup_globals.IMPINFO)
if range: # remove existing details
gvars.out.put("Value and range for %s %s set to %s [%s]."%(type,name,value,range),setup_globals.IMPINFO)
del self[(loc2,name)]
self.locations[name].remove(loc2)
quit = 0
else:
gvars.out.put("Value and range for %s %s set to %s %s."%(typ2,name,val2,self[(loc2,name)]["RANGE"]),setup_globals.IMPINFO)
else:
gvars.out.put('\nWARNING: Parameter %s defined in both\n%s (default %s) and \n%s (default %s)'
%(name, loc2, val2, location, value),setup_globals.WARN)
gvars.out.put("Ignoring second instance",setup_globals.WARN)
gvars.out.pop()
if quit: return
if not self.warn:
if self.has_key((location,name)): # same combo of both is there
gvars.out.push()
gvars.out.put('\nWARNING: Parameter %s is defined in %s multiple times. Ignoring new instance\n'
% (name,location),setup_globals.WARN)
gvars.out.pop()
return
if not self.typemap.has_key(type):
raise SetupError("Invalid type %s for parameter %s" % (type,name))
ans = { "TYPE":type,
"VALUE":value,
"CONST":const,
"RANGE": self.parseRange(range,type,name),
"COMMENT":comment}
self[(location,name)] = ans
if not self.locations.has_key(name): self.locations[name] = []
self.locations[name].append(location)
# Takes a range object and returns a string describing the range object
def printRange(self, rng):
if not rng: return "Valid Values: Unconstrained"
ans = []
for a in rng:
if type(a) == type("STRING"):
ans.append('"%s"' % a)
continue
min = a["min"]
max = a["max"]
if not min: min = "-INFTY"
if not max: max = "INFTY"
if min == max:
ans.append("%s" % min)
else:
ans.append("%s to %s" % (min,max))
return "Valid Values: %s" % ", ".join(ans)
def write_rp_info(self,fname=None,prefix=""):
if not fname:
#fname = os.path.join(gvars.flash_src_dir,gvars.project_build_dir,setup_globals.SETUP_PARAMS_FILENAME)
fname = os.path.join(gvars.project_setup_dir, setup_globals.SETUP_PARAMS_FILENAME)
out = setup_globals.IndentedOutput(4, open(fname, 'w'))
rplist = [ (loc,name,a) for ((loc,name),a) in self.items() ]
rplist.sort()
currunit = None
out.push()
for (unitname,rpname,rpinfo) in rplist:
if not unitname.startswith(prefix): continue
# Starting a new unit
if currunit != unitname:
out.pop()
out.put("")
out.put(unitname)
currunit = unitname
out.push()
if rpinfo["TYPE"] == "DOC":
out.put(rpname)
else:
if (rpinfo["CONST"]):
out.put('%s [%s] CONSTANT [%s]' % (rpname, rpinfo["TYPE"],rpinfo["VALUE"]))
else:
out.put('%s [%s] [%s]' % (rpname, rpinfo["TYPE"],rpinfo["VALUE"]))
if rpinfo["TYPE"] in ["INTEGER","REAL","STRING"]:
out.push()
out.put(self.printRange(rpinfo["RANGE"]))
out.pop()
out.push()
out.put(rpinfo["COMMENT"])
out.pop()
out.pop()
out.file.close()
def writeDuplications(self,fname):
out = setup_globals.IndentedOutput(4, open(fname,"w"))
names = self.locations.keys()
# for each name find the number of locations where its type is not DOC
# if this number > 1, then consider it duplicated
names = filter(lambda x: len(
filter(lambda y: self[(y,x)]["TYPE"] != "DOC",
self.locations[x])
) > 1,names) # only multiply defined ones
names.sort()
for name in names:
out.put(name + " defined in the following locations")
out.push()
for loc in self.locations[name]:
if self[(loc,name)]["TYPE"] == "DOC": continue
if self[(loc,name)]["CONST"]:
out.put("%s CONSTANT [%s]" % (loc,self[(loc,name)]["VALUE"]) )
else:
out.put("%s [%s]" % (loc,self[(loc,name)]["VALUE"]) )
out.pop()
out.put("")
# find names with no comments
# names = all names with one definition
badnames = []
for (x,y) in self.locations.items():
for loc in y:
if not self[(loc,x)].get("COMMENT",None): badnames.append((x,loc))
# badnames = all names where no COMMENT
if badnames:
out.put("Runtime Parameters without any Comments")
out.put("---------------------------------------")
oldname = None
for (name,loc) in badnames:
if oldname != name:
out.put("\n"+name)
oldname = name
out.push()
out.put("no comment in %s/Config" % loc)
out.pop()
out.file.close()
# info about RuntimeParameter functions are hard coded into this function
def genRulesCode(self,rpname,rptype,range):
def clean(obj,typ):
"add a . if obj consists only of numbers and we want it to be REAL"
if typ == "INTEGER": return str(obj)
rv = str(obj)
if rv == "TINY": return "TINY(1.0)"
if rv == "-TINY": return "-TINY(1.0)"
suff = ""
# check if obj ends with "+","-"
# adjust the given numbers in that direction by a
# small amount
if rv[-1] in ["+","-"]:
suff = rv[-1]
rv = rv [:-1]
suff = suff+"EPSILON(1.0)"
if re.match("^[0-9]*$",rv): # only numbers found
rv = rv + ".0"
return rv+suff
if not range: return ""
if rptype not in ["STRING","INTEGER","REAL"]: return ""
rv = ""; rvrest = ""
if rptype == "STRING": # we are doing it for a string
numVals = len(range)
mlen= 0
for val in range:
if type(val) != type("STRING"):
raise SetupError("'%s' is not a string value for Runtime Parameter '%s'" % (val,rpname))
mlen = max(mlen,len(val))
tpl = '"%%-%ds"' % mlen
validValues = [ tpl % val for val in range ]
rvrest = ' call rp_rules( "%s", %d, (/ %s /) )' % (rpname, numVals,", ".join(validValues))
rv1 = ""
elif rptype in ["INTEGER","REAL"]: # integers or reals
# an example number of the given type
if rptype == "INTEGER":
typ= "1"
else: typ= "1.0"
numVals = len(range)
minVals = []
maxVals = []
for rangespec in range:
if rangespec["min"]:
minVals.append( clean(rangespec["min"],rptype))
else: minVals.append( "-HUGE(%s)" % typ)
if rangespec["max"]:
maxVals.append( clean(rangespec["max"],rptype))
else: maxVals.append( "HUGE(%s)" % typ)
rvrest = ' call rp_rules( "%s", %d, (/ %s /), (/ %s /) )' % \
(rpname,numVals, ", ".join(minVals), ", ".join(maxVals))
if (len(rvrest) > 0):
rv1 = ""
while len(rvrest) > 132:
rv1 = rv1 + rvrest[:131] + "&\n"
rvrest = "&" + rvrest[131:]
rv = rv1 + rvrest + "\n"
return rv
[docs] def write_default_par(self):
"""write out a default.par which has all default values commented"""
header = ['Copy before editing!!!',
'Created by the setup script.',
'',
'Contains default values for all runtime parameters specific to this simulation',
'']
#fname = os.path.join(gvars.flash_src_dir,gvars.project_build_dir,setup_globals.RP_DEFAULT_PAR_FILENAME)
fname = os.path.join(gvars.project_setup_dir, setup_globals.RP_DEFAULT_PAR_FILENAME)
f = LazyFile(fname)
f.write('## '+"\n## ".join(header)+"\n\n")
locitems = self.locations.items()
locitems.sort()
for (rpname,rplocations) in locitems:
if len(rplocations) != 1:
gvars.out.put("%s found in multiple locations %s!! Programming Error?" % (rpname,rplocations))
loc = rplocations[0]
rpinfo = self[(loc,rpname)]
# ignore documentation things and CONST runtime parameters (which cannot be changed by the user anyway)
if rpinfo["TYPE"] in ["DOC","CONST"]: continue
if rpinfo["TYPE"] == "BOOLEAN":
rpval = ".%s." % rpinfo["VALUE"]
else: rpval = rpinfo["VALUE"]
f.write("# %-30s = %s\n" % (rpname,rpval))
f.write("\n")
f.close()
# info about RuntimeParameter functions are hard coded into this function
def write_code(self, config_info):
header = '! Runtime-settable parameter initializations;\n'\
'! generated by setup script.\n'\
'! Do not edit!\n\n'\
'subroutine rp_initParameters(parmfile)\n\n'\
'character(len=*) :: parmfile\n'\
'call rp_createParmList()\n'\
'return\n'\
'end subroutine rp_initParameters\n\n\n'\
'subroutine rp_createParmList ()\n\n'\
'use RuntimeParameters_interface, ONLY : RuntimeParameters_add\n'\
'use RuntimeParameters_data, ONLY : TYPE_CONST, TYPE_VAR\n\n'\
'implicit none\n\n'\
'#include "constants.h"\n'\
'#include "rp_rules.h"\n\n'
#fname = os.path.join(gvars.flash_src_dir,gvars.project_build_dir,setup_globals.RP_INIT_PARAMS_FILENAME)
fname = os.path.join(gvars.project_setup_dir, setup_globals.RP_INIT_PARAMS_FILENAME)
f = LazyFile(fname)
f.write(header)
locationslist = self.locations.items()
locationslist.sort()
for (rpname,rplocations) in locationslist:
if len(rplocations) != 1:
gvars.out.put("%s found in multiple locations %s!! Programming Error?" % (rpname,rplocations))
loc = rplocations[0]
rpinfo = self[(loc,rpname)]
# Ignore "parameters" which are documentation
if rpinfo["TYPE"] == "DOC": continue
if rpinfo["TYPE"] == "BOOLEAN":
rpvalue = ".%s."
else: rpvalue = "%s"
rpvalue = rpvalue % rpinfo["VALUE"]
#print rpvalue
if rpinfo["TYPE"] == "REAL":
if rpvalue == "TINY":
rpvalue = "TINY(1.0)"
elif rpvalue == "HUGE":
rpvalue = "HUGE(1.0)"
elif rpvalue == "-HUGE":
rpvalue = "-HUGE(1.0)"
#elif rpinfo["TYPE"] == "INTEGER":
# if rpvalue == "MAXINT"
# rpvalue = "
if rpinfo["CONST"]:
f.write(' call RuntimeParameters_add( "%s", %s, %s)\n'%(rpname, rpvalue, "TYPE_CONST"))
else:
f.write(' call RuntimeParameters_add( "%s", %s)\n'%(rpname, rpvalue))
# The code for interval and enum checking should come here
rules = self.genRulesCode(rpname,rpinfo["TYPE"],rpinfo["RANGE"])
if rules: f.write(rules)
# #Allocate enough io_plot_var names for all variables in the simulation.
# #Not needed here any more, now instead calling addRP from get_rp_info (in unitUtils.py) for these.
# numRegularPlotVars = config_info['max_plot_vars']
# for i in range(1,numRegularPlotVars+1):
# f.write(' call RuntimeParameters_add( "%s", %s)\n'%(('plot_var_' + '%d'%i), '"none"'))
f.write('\nreturn\nend subroutine rp_createParmList\n\n')
f.close()