Skip to content

Commit 9eb0603

Browse files
committed
Get configuration and interpretation into builders going.
1 parent 89b0129 commit 9eb0603

File tree

10 files changed

+201
-21
lines changed

10 files changed

+201
-21
lines changed

doc/configuration.org

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,66 @@ The GGD system is layered and allows the user/programmer access to each layer.
66

77
* Configuration File Syntax
88

9-
The GGD configuration file syntax is parsed by Python's =configparser= module. This syntax is also sometimes known as "INI". It consists of a number of named sections which hold a number of key/value pairs.
9+
The GGD configuration file syntax is parsed by Python's =configparser= module. This syntax is also sometimes known as "INI". It consists of a number of named sections each holding a number of key/value pairs.
1010

1111
#+BEGIN_EXAMPLE
12-
[section name]
12+
[name]
1313
key1 = value1
1414
#+END_EXAMPLE
1515

1616
** Syntax Extensions
1717

18-
The GGD configuration layer will evaluate each section of key/value pairs in a limited Python environment. This allows any given value to reference a key from the same section in order to use its value. It also allows expression of complex data structures, units, arithmetic. In principle this allows a high degree of programming and a balance should be struck. In order to keep the configuration simple, complex programming should be put into the builder.
18+
The GGD configuration layer will process the file in a series of steps that add some features beyond the basic syntax. Care should be exercised in exploiting these features. If the configuration becomes complex consider if it would be better to move this complexity into the [[./builders.org][builder]] code.
19+
20+
*** Evaluation
21+
22+
The values in the parsed configuration file are evaluated by the Python interpreter in order. Within one section, prior keys may be referenced in subsequent values. For example:
23+
24+
#+BEGIN_EXAMPLE
25+
[mybuilder]
26+
x = 21
27+
y = x*2
28+
#+END_EXAMPLE
29+
30+
results in =y= being 42. Referencing a variable before it is defined is an error.
1931

2032
*** Specifying units
2133

22-
All numerical quantities that have units must have them explicitly given in the configuration. This is done with the =Q()= function ("Q" for "quantity").
34+
All numerical quantities that have units *must* have them explicitly given in the configuration. If not, they will be caught as an error during geometry generation. Units are specified with the =Q()= function ("Q" is for "quantity"). This is an abbreviation for the [[http://pint.readthedocs.org/en/0.5.1/tutorial.html][=pint.UnitRegistry.Quantity=]] method and as such can be "called" in the configuration file in any manner it could be called directly from Python.
2335

2436
#+BEGIN_EXAMPLE
25-
[builder mydetector]
37+
[mybuilder]
2638
width = Q("10m")
27-
height = 10*width
28-
depth = 10*height
39+
height = Q(10, 'meter')
40+
depth = Q("10*m")
2941
#+END_EXAMPLE
3042

43+
*** Data structures
3144

45+
Because values are evaluated by the Python interpreter the configuration values may take the form of any valid Python data structure. One particularly useful one is a list to give a vector of values. For example:
3246

33-
The content of the configuration file is parsed into a Python data structure by GGD and then interpreted following these conventions.
47+
#+BEGIN_EXAMPLE
48+
[mybuilder]
49+
offset = [Q("1m"), Q("2m"), Q("3m")]
50+
#+END_EXAMPLE
51+
52+
* Interpretation
53+
54+
After the configuration data is parsed and evaluated as above it is interpreted by GGD.
3455

3556
** Builder sections
3657

37-
Sections starting with "=[builder NAME]=" are interpreted as being for a builder. Any key/value pairs given will be passed to the builder named "=NAME=" if it is created. Certain keys are reserved by GGD:
58+
Each configuration section corresponds to a single instance of a builder object (see [[./builders.org][Builders]]) of the same name. Any key/value pairs given will be passed to the builder for that name, if it is created. Builder creation starts at a given builder section or the first section if none is given.
59+
60+
Certain keys are reserved by GGD for interpretation:
3861

3962
- =class= :: The fully qualified class name (eg "=module.submodule.MyBuilderClass=").
40-
- =subbuilders= :: A space or comma separated list of builder names to be given to the builder as sub-builders.
63+
- =subbuilders= :: A list of builder instance names to be given to the builder as sub-builders.
4164

4265
Beyond these reserved keys each builder is free to expect its own set of keys.
4366

44-
** Example
67+
68+
* Examples
4569

4670
A working example is in the source at [[../python/gegede/examples/lar.cfg][lar.cfg]].
4771

python/gegede/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import pint
22
units = pint.UnitRegistry()
33
Quantity = units.Quantity
4+
5+

python/gegede/configuration.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,74 @@
22
'''
33
gegede configuration
44
'''
5+
import os
6+
from collections import OrderedDict
57

68
def parse(filenames):
7-
'''Parse the filename, return the parser object.'''
9+
'''Parse configuration files.
10+
11+
Return the parser object.'''
12+
813
try: from ConfigParser import SafeConfigParser
914
except ImportError: from configparser import SafeConfigParser
1015
cfg = SafeConfigParser()
1116
cfg.optionxform = str # want case sensitive
12-
cfg.read(filenames)
17+
18+
if isinstance(filenames, type("")):
19+
filenames = [filenames]
20+
for fname in filenames:
21+
if not os.path.exists(fname):
22+
raise ValueError, 'No such file: %s' % fname
23+
cfg.read(fname)
1324
return cfg
25+
26+
27+
def cfg2pod(cfg):
28+
'''
29+
Convert a ConfigParser object to a plain-old-data structure
30+
'''
31+
pod = OrderedDict()
32+
for secname in cfg.sections():
33+
secdat = OrderedDict()
34+
for k,v in cfg.items(secname):
35+
secdat[k] = v
36+
pod[secname] = secdat
37+
return pod
38+
39+
def make_class(fqclass):
40+
mod, cls = fqclass.rsplit('.',1)
41+
exec('import %s' % mod) # better way?
42+
return eval(fqclass)
43+
44+
def make_value(v, **kwds):
45+
from . import Quantity
46+
return eval(v, globals(), dict(Q=Quantity, **kwds))
47+
48+
49+
def evaluate(pod):
50+
'''
51+
Evaluate and replace each value.
52+
'''
53+
ret = OrderedDict()
54+
for secname, secdat in pod.items():
55+
newdat = OrderedDict()
56+
for k,v in secdat.items():
57+
if k == 'class':
58+
newdat[k] = make_class(v)
59+
continue
60+
newdat[k] = make_value(v, **newdat)
61+
ret[secname] = newdat
62+
return ret
63+
64+
def configure(filenames):
65+
'''
66+
Return evaluated configuration
67+
'''
68+
cfg = parse(filenames)
69+
assert cfg.sections()
70+
pod = cfg2pod(cfg)
71+
assert pod
72+
dat = evaluate(pod)
73+
assert dat
74+
return dat
75+

python/gegede/examples/builders.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ class SimpleBuilder(gegede.builder.Builder):
1212
'''
1313

1414
def configure(self, dx='1m', dy='1m', dz='1m', mat='Air', **kwds):
15+
#print 'Configuring "%s"' % self.name
1516
self.box_mat = mat # assume made somewhere else
1617
self.box_dim = (dx,dy,dz)
1718

1819
def construct(self, geom):
20+
#print 'Constructing "%s"' % self.name
1921
shape = geom.shapes.Box(self.name + '_box_shape', *self.box_dim)
2022
lv = geom.structure.Volume(self.name+'_volume', material=self.box_mat, shape=shape)
2123
self.add_volume(lv)

python/gegede/examples/lar.cfg

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
#+BEGIN_EXAMPLE
2-
[builder world]
1+
[world]
32
subbuilders = site
43
class = gegede.examples.lar.WorldBuilder
5-
size = 50m
4+
size = Q("50m")
65
material = Rock
76

8-
[builder site]
7+
[site]
98
subbuilders = tank
109
class = gegede.examples.lar.TankBuilder
11-
#+END_EXAMPLE
10+

python/gegede/interp.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env python
2+
'''Some functions that to the interpretation of evaluated
3+
configuration data into builder objects.
4+
'''
5+
6+
def get_builder_config(dat, name):
7+
'''
8+
Return the builder's configuration section of the given <name>.
9+
10+
If <name> is None, return first builder in <dat>.
11+
'''
12+
if name is None:
13+
return dat.items()[0]
14+
return name, dat[name]
15+
16+
def make_builder(dat, name = None):
17+
'''Return the a builder object of the given name from the
18+
configuration data <dat> and recursively call on any subbuilders.
19+
If no name is given return the first found.
20+
'''
21+
bname, bdat = get_builder_config(dat, name)
22+
bobj = bdat['class'](bname)
23+
subbuilders = bdat.get('subbuilders',list())
24+
for sbname in subbuilders:
25+
sb = make_builder(dat, sbname)
26+
bobj.builders.append(sb)
27+
return bobj
28+

python/gegede/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
'''
3+
High level main functions.
4+
'''
5+
import gegede.configuration
6+
import gegede.interp
7+
import gegede.builder
8+
9+
10+
11+
def generate(filenames, world_name = None):
12+
'''
13+
Return a geometry object generated from the given configuration file(s).
14+
'''
15+
assert filenames
16+
cfg = gegede.configuration.configure(filenames)
17+
assert cfg
18+
wbuilder = gegede.interp.make_builder(cfg, world_name)
19+
gegede.builder.configure(wbuilder, cfg)
20+
geom = gegede.construct.Geometry()
21+
gegede.builder.construct(wbuilder, geom)
22+
return geom

tests/test_configuration.cfg

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1-
[builder mybuilder]
1+
[mybuilder]
22
class = gegede.examples.builders.SimpleBuilder
3+
subbuilders = ["sb1", "sb2"]
4+
nickname = "Digger Dan"
35
width = Q("10m")
46
height = 10*width
57
depth = height*1.23
8+
dims = [width, height, depth]
9+
offset = [Q("1m"), Q("2m"), Q("3m")]
10+
11+
[sb1]
12+
class = gegede.examples.builders.SimpleBuilder
13+
subbuilders = ["ssb1", "ssb2"]
14+
15+
[sb2]
16+
class = gegede.examples.builders.SimpleBuilder
17+
18+
[ssb1]
19+
class = gegede.examples.builders.SimpleBuilder
20+
[ssb2]
21+
class = gegede.examples.builders.SimpleBuilder

tests/test_configuration.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
#!/usr/bin/env python
22

3+
import os
34
import gegede.configuration as ggdconf
45

56
def test_read():
6-
cfg = ggdconf.parse(__file__.replace('.py','.cfg'))
7-
print cfg
7+
fname = os.path.splitext(__file__)[0]+'.cfg'
8+
cfg = ggdconf.parse(fname)
9+
pod = ggdconf.cfg2pod(cfg)
10+
dat = ggdconf.evaluate(pod)
11+
assert dat
12+
13+
14+
def test_configure():
15+
fname = os.path.splitext(__file__)[0]+'.cfg'
16+
cfg = ggdconf.configure(fname)
17+
worldname, worldcfg = cfg.items()[0]
18+
assert 2 == len(worldcfg['subbuilders'])

tests/test_main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import gegede.main
5+
6+
def test_main():
7+
fname = os.path.dirname(os.path.realpath(__file__))+'/test_configuration.cfg'
8+
g = gegede.main.generate(fname)
9+
10+
assert g
11+
assert g.store.shapes
12+
if not g.store.matter:
13+
print "You still haven't implemented this, huh?"
14+
assert g.store.structure

0 commit comments

Comments
 (0)