11.4 Simulation and Visualization Scripts
11.4.10 Allowing Physical Units in Input Files
Many simulators are driven by text files, and a simple example is shown in the previous section. Now we shall demonstrate how scripting tools can be applied to improve the interface to such file-driven simulators. Two parallel ideas will be followed:
1. Add the possibility to specify numbers with physical units in the input file, parse the file, perform unit conversions where required, and write out a file with the syntax required by the simulator (i.e., no units).
2. Parse the input file, build a GUI, fill in values in the GUI, and build a new input file.
In the case we make a GUI, it should be capable of handling numbers with physical units. The present section concerns the first idea, while the second is the topic of Chapter 11.4.11.
The Syntax of the Input File. The syntax of input files is usually highly dependent upon the simulator. To proceed we therefore need a sample syntax to work with. Our choice of syntax is taken from the Diffpack [15] software and illustrated in Chapter 11.4.9. Each line in the input file represents a command.
The commands of interest here are those assigning values to parameters. Such lines are on the form
set parameter = value ! comment
The name of the parameter appears between thesetkeyword and the first= character. Everything after the exclamation mark is a comment and stripped off in the interpretation of the line. After stripping the optional comment, the value of the parameter is the text after the first=sign.
Here is an example of an input file with legal syntax:
set time parameters = dt=1e-2 t in [0,10]
! just a comment
set temperature = 298 ! initial temperature set sigma_x = 20.0 ! horizontal stress set sigma_y = 0.2 ! vertical stress set pressure = 0.002
set base pressure = 0 set height = 80
set velocity = 100 ! inflow velocity
Input Files with Physical Units. Computational scientists know well that physical units constitute a common source of errors. We therefore propose to enhance the input file syntax with the possibility of adding units. To this end, we require that the reference unit of a physical parameter appears in square brackets right after the exclamation mark. The specification of the unit must follow the conventions in the PhysicalQuantites module in the ScientificPython package (see Chapter 4.4.1). The value of a parameter can then optionally have a unit. This unit does not need to be identical to the reference unit, since a main purpose of the functionality is to automatically convert a given value to the right number in reference units.
The structure of the enhanced line syntax looks like set parameter = value unit ! [ref_unit] some comment or
set parameter = value ! [ref_unit] some comment ifvalueis given in reference units, or
set parameter = value ! some comment
if unit specifications are disabled. An input file following this syntax may read
set time parameters = dt=1e-2 t in [0,10]
! just a comment
set temperature = 298 K ! [K] initial temperature set sigma_x = 20.0 bar ! [bar] horizontal stress set sigma_y = 20.0 kN/m**2 ! [bar] vertical stress set pressure = 200.0 Pa ! [bar]
set base pressure = 0 ! [bar]
set height = 80
set velocity = 100 ! inflow velocity
598 11. More Advanced GUI Programming
As we see, the parameters measured in bar are given values in other compat- ible units, here Pascal and kilo Newton per square meter.
A Script for Parsing Files with Units. Our line-oriented file syntax is very well suited for regular expressions. Each line of interest (those assigning values to parameters) matches the regular expression
set (.*?)\s*=\s*([^!]*)\s*(!?.*)’
Note that we have here defined three groups: the parameter name, the value, and the optional comment.
The comment group may contain the specification of a reference unit so a relevant regular expression for further processing of the comment part reads
!\s*\[(.*?)\](.*)
Two groups are defined here: the unit and the rest of the comment. If the comment does not match this pattern, the comment does not contain the specification of a unit.
The value of the parameter can take many forms. We need to find out whether or not the value is a number followed by a unit. If this is not the case, no unit conversion is needed, and the value can be used further as is. To detect if the value is a number and a unit, we can use the regular expression
([0-9.Ee\-+]+)\s+([A-Za-z0-9*/.()]+)
The first group represents the number, but we could alternatively use safer and more sophisticated regular expressions from Chapter 8.2.3 for match- ing real numbers. The second group has a pattern containing the possible characters in legal physical units (examples of such units are kN/m**2 and s**0.5).
The code for processing an input file line by line can now take the following form:
def parse_input_file(lines):
line_re = re.compile(r’set (.*?)\s*=\s*([^!]*)\s*(!?.*)’) comment_re = re.compile(r’!\s*\[(.*?)\](.*)’)
value_re = re.compile(r’([0-9.Ee\-+]+)\s+([A-Za-z0-9*/.()]+)’) parsed_lines = [] # list of dictionaries
output_lines = [] # new lines without units for line in lines:
m = line_re.search(line)
# split line into parameter, value, and comment:
if m:
parameter = m.group(1).strip() value = m.group(2).strip() try: # a comment is optional
comment = m.group(3) except:
comment = ’’
ref_unit = None; unit = None # default values
if comment:
# does the comment contain a unit specification?
m = comment_re.search(comment) if m:
ref_unit = m.group(1)
# is the value of the form ’value unit’?
m = value_re.search(value) if m:
number, unit = m.groups()
else: # no unit, use the reference unit number = value; unit = ref_unit value += ’ ’ + ref_unit
# value now has value _and_ unit
# convert unit to ref_unit:
pq = PhysicalQuantity(value) pq.convertToUnit(ref_unit) value = str(pq).split()[0]
# convert value (str) to float, int, list, ..., str:
value = scitools.misc.str2obj(value) output_lines.append(’set %s = %s %s\n’ % \
(parameter, value, comment)) parsed_lines.append({’parameter’ : parameter,
’value’ : value, # in ref_unit
’ref_unit’ : ref_unit,
’unit’ : unit,
’comment’ : comment})
else: # not a line of the form: set parameter = value output_lines.append(line)
parsed_lines.append(line) return parsed_lines, output_lines
The result of this function consists of two data structures:
– parsed_linesis a list of dictionaries and lines. Each dictionary holds var- ious parts of the lines assigning values to parameters, such as parameter name, the value, the reference unit, the unit, and the comment. Lines that do not assign values to parameters are inserted as strings inparsed_lines. The purpose of this data structure is to enable easy construction of a GUI, or conversion to other data formats if desired.
– output_linesis a list of all lines, where numbers with units are converted to the right number in the specified reference unit before the unit is removed.
Theparse_input_file function shown above is contained in the module file src/py/examples/simviz/inputfile_wunits.py
To convert an input file with physical units to an input file with the specified simulator syntax (i.e., no units), we load the file into a list of lines, callparse_input_file, and write out each element in theoutput_lineslist to a new input file. The sample lines with physical units given previously are transformed to
600 11. More Advanced GUI Programming set time parameters = dt=1e-2 t in [0,10]
! just a comment
set temperature = 298.0 ! [K] initial temperature set sigma_x = 20.0 ! [bar] horizontal stress set sigma_y = 2e-06 ! [bar] vertical stress set pressure = 0.002 ! [bar]
set base pressure = 0.0 ! [bar]
set height = 80
set velocity = 100 ! inflow velocity
Note that the original whitespace-based formatting is lost. Usingparsed_lines and proper format statements instead of dumpingoutput_lines, one can quite easily get nicer output formatting with aligned values and comments.
Exercise 11.13. Convert command file into Python objects.
Suppose you have a file with syntax like set heat conduction = 5.0
set dt = 0.1
set rootfinder = bisection
set source = V*exp(-q*t) is function of (t) with V=0.1, q=1 set bc = sin(x)*sin(y)*exp(-0.1*t) is function of (x,y,t) The first three lines follow the syntax
set variable = value
wherevariablewith spaces replaced by (e.g.) underscores yields a legal vari- able name in Python and value is a text that can be evaluated (by eval) and turned into Python objects of the appropriate type. The other two lines exemplify an extended syntax of the form
set variable = expression is function of (var1, var2, ...) \ with prm1=0.1, prm2=-1, prm3=7, prm4=-0.0001
where var1, var2, etc., are independent variables in expression, while prm1, prm2, and so on are parameters in the same expression. The with part is optional. Assume that the completeset“command” appears on a single line (no split as done above because of page width limitations in a book).
Make a Python module that parses input files with such syntax via regular expressions and for each line makesvariable available as a Python variable holding the appropriatevalue. For the function expressions you can use class StringFunction(Chapter 12.2.1). It is convenient to collect all these variables in a dictionary and when desired, update locals() or globals() with this dictionary to introduce the variables in the local or global namespace.
The module should be able to read the commands given above and then run the following interactive session:
>>> import mod # module with functionality from this exercise
>>> newvars = mod.parse_file(testfile)
>>> globals().update(newvars) # let new variables become global
>>> heat_conduction, type(heat_conduction) (5.0, <type ’float’>)
>>> dt, type(dt)
(0.10000000000000001, <type ’float’>)
>>> rootfinder, type(rootfinder) (’bisection’, <type ’str’>)
>>> source, type(source)
(StringFunction(’V*exp(-q*t)’, independent_variables=(’t’,), q=1, V=0.10000000000000001), <type ’instance’>)
>>> bc, type(bc)
(StringFunction(’sin(x)*sin(y)*exp(-0.1*t)’,
independent_variables=(’x’, ’y’, ’t’), ), <type ’instance’>)
>>> source(1.22) 0.029523016692401424
>>> bc(3.14159, 0.1, 0.001) 2.6489044508054893e-07