SimpleConfig
Introduction
Mu2e uses two langauges for run-time configuration, SimpleConfig and FHiCL (.fcl). The SimpleConfig langague is used for the geometry file, the conditions file, the global constants file and the configuration of the two of the event generator modules, EventGenerator and G4BeamlineGenerator. This language is the topic of this web page.
FHiCL files end with the extenstion .fcl and
are used to configure art itself, modules that run under art and services
that run under art. It is described
on the web pages describing
art run-time configuration
and art reconstruction modes.
The reasons for two langauges is historical. At the time that Mu2e started FHiCL was not available and its precursor was judged to be inappropriate for the geometry file and the other uses mentioned above; although the language was much more powerful, it was visually awkward because the type information was so verbose that it obscured the actual content. So SimpleConfig was adopted for these purposes. Most of the information currently accessed via SimpleConfig will, some day, be held in databases and will be accessed via a database interface. For the development of the Offline Mu2e software this information was put into plain text files in order to kep the devlopment cycle as simple a possible.
Syntax Basics
The fragment below is taken from the section of the geometry file,
Mu2eG4/src/geom_01.txt
, that describes the geometry
of the calorimeter
vector<double> calorimeter.calorimeterCenter = {0.,0.,12700.}; // G4BL: 20729-7929=12800. double calorimeter.rInscribed = 360.; string calorimeter.calorimeterFillMaterial = "DSVacuum"; int calorimeter.numberOfVanes = 4; int calorimeter.nCrystalRSlices = 11; int calorimeter.nCrystalZSlices = 44; double calorimeter.crystalHalfLong = 55.01; double calorimeter.crystalHalfTrans = 15.; string calorimeter.crystalMaterial = "Lyso_01"; bool calorimeter.vaneBoxVisible = true;
It is Mu2e policy that all unit-ful numbers should be given in the standard Mu2e units of mm, MeV, ns; there is no technological enforcement of this policy.
The syntax in the above fragment is somewhat reminiscent of C or C++; each definition must start on a new line and must end in a semi-colon. The comment delimiter is // and it may start in any column; the comment delimiter and all text to its right is simply discarded. A legal definition has the format:
type name = value;
There are only 7 legal types:
string, int, double, bool, vector<string>, vector<int>, and vector<double>
The only significant whitespace is the separation between the type and the name. Therefore the following are equivalent:
double calorimeter.crystalHalfTrans = 15.; double calorimeter.crystalHalfTrans=15.; double calorimeter.crystalHalfTrans = 15.; double calorimeter.crystalHalfTrans = // An odd spot for a comment. 15. ; // The final semi=colon is required.
In the above example, the names use a dot as a separator, reminiscent of references to member data in a C++ class or struct. The dot has no significance to the SimpleConfig language but we recommend its use for two reasons. First, it makes it easier for people to read the file and second, it will make it easier to write scripts to migrate this information from SimpleConfig format to a database or to some other text format that does understand structures. Names may contain only letters, numbers and the dot character; case is significant and the first character must be a letter.
If a string value ( the right hand side of the equals sign) contains no white space and it only contains letters and numbers, then it may be quoted or unquoted. Otherwise it must be quoted.
There is a bug in the parser illustrated by,
string name = "The parser will get confused by this: //";
The parser will think that the // starts a comment and will discard the closing quote and semi-colon, leaving a malformed definition. It will issue an error.
When specifying numeric values, do not use comma or dot to delimit thousands; this will confuse the parser. The parser does not understand the use of comma instead of a dot to delimit the fractional part of a decimal number.
Another file in SimpleConfig format can be included, as illustrated
by the following fragment from geom_01.txt
#include "Mu2eG4/test/ttracker_v2.txt"
The syntax of an include is reminiscent of a CPP include; it must start in the first column and it does not end in a semi-colon. This behaves exactly as if one had placed the contents of the named file at that spot in the input file. Includes can be nested to arbitrary depth.
If the SimpleConfig parser encounters the definition of a name that has already been defined, the default behaviour is to replace the previous definition with the new one. If the two definitions are of different types, it will issue an error and stop execution. Optionally, the parser will print a message announcing that the replacement has occured. There is also an option to forbid replacement and to stop the program if a second definition of any parameter is found. How to control this behaviour will be discussed in the sections on reading a SimpleConfig file and use of SimpleConfig in Services and event generators.
Reading a SimpleConfig File
Most people should not read SimpleConfig files directly. If, for example, you want to know the number of vanes in the calorimeter, get that information from the Calorimeter object in the GeometryService,
GeomHandle<Calorimeter> calGeom; cout << "Number of calorimeter vanes: " << calGeom->nVanes() << endl;
If you do this then, when we migrate away from SimpleConfig to a database, your code will continue to work as is.
For those who do need to read a SimpleConfig file, the rest of this section discusses how. The signature of the constructor is:
SimpleConfig::SimpleConfig( const string& filename, bool allowReplacement = true, bool messageOnReplacement = false, bool messageOnDefault = false):
The filename argument specifies the filename of the input file; the filename must be specified as a pathname relative to the directories defined by the environment variable MU2E_SEARCH_PATH. The directories are searched in order and the first match is taken. There is a separate web page that describes file search paths in more detail.
If allowReplacement is true then, if the parser encounters a redefintion of an existing parameter, it will discard all previous definitions and keep the last definition; if allowReplacement is false, then the parser will throw an exception when it encounters a redefinition of a parameter.
If messageOnReplacement is true, the parser will produce a message whenever a replacement occurs.
If messageOnDefault is true, then the parser will produce a message when a parameter is not found in the file but the caller has specified a default value.
The following fragment illustraces the syntax for accessing parameters. It refers to the fragment of geom_01.txt shown in the previous section:
SimpleConfg config("geom_01.txt"); int nVanes = config.getInt ("calorimeter.nVanes"); double halfTrans = config.getDouble("calorimeter.crystalHalfTrans"); string fillMat = config.getString("calorimeter.FillMaterial"); bool vaneBoxVisible = config.getBool ("calorimeter.vaneBoxVisible");
In the first example, SimpleConfig will look for the parameter named calorimeter.nVanes. If that parameter is present, SimpleConfig will return its value; if it is absent, SimpleConfig will throw an exception. It is good practice for the the C++ variable names to match parameter names in the geometry file; but this is not enforced by any mechanism. Unlike FHiCL, the accessors are strongly typed; the following will produce an error because the parameter calorimeter.nVanes was declared as an int in the geometry file but the code wants it to be a double..
double nVanes = config.getDouble("calorimeter.nVanes"); <font color=red>// Error !</font>
All of the accessors have a second form that allows for a user supplied default value:
int nVanes = config.getInt ("calorimeter.nVanes", 4 ); double halfTrans = config.getDouble("calorimeter.crystalHalfTrans", 16.); string fillMat = config.getString("calorimeter.FillMaterial", "DSVacuum"); bool vaneBoxVisible = config.getBool ("calorimeter.vaneBoxVisible", false);
In the first example, SimpleConfig will look for the parameter named calorimeter.nVanes. If that parameter is present, SimpleConfig will return its value; if it is absent, SimpleConfig will return the user supplied default value. Be smart about choosing whether or not to use default values: they are a good choice for verbosity levels and graphics options but they are usually a poor choice for core information.
The accessors for vector valued names have a slightly different syntax:
std::vector<double> center; config.getVectorDouble("calorimeter.calorimeterCenter", center); int nRequired(3); config.getVectorDouble("calorimeter.calorimeterCenter", center, nRequired); std::vector<double> default(0.,0.,12700.); config.getVectorDouble("calorimeter.calorimeterCenter", center, default); config.getVectorDouble("calorimeter.calorimeterCenter", center, default, nRequired);
And similarly for getVectorInt and getVectorString. There is no getVectorBool but we can add it if anyone needs it.
In the first call to getVectorDouble, SimpleConfig will look for a parameter with the given name and with the type vector<double>; if it finds such a parameter, it will copy its contents into the C++ variable center; it will throw an exception if the parameter is absent. The second call will do the same except that it will throw an exception if the definition of the parameter does not contain exactly three values. In the third call, if the parameter is not found, SimpleConfig will copy the contents of the variable default into the variable center. In the last example, if the parameter is present in the file, it must have exactly three values.
If nRequired is set to -1, SimpleConfig will not check the number of values in the definition the parameter and will copy all that it finds into the variable center.
Finally there is one special accessor:
CLHEP::Hep3Vector center = config.getHep3Vector("calorimeter.calorimeterCenter"); CLHEP::Hep3Vector default(0.,0.,12700.); CLHEP::Hep3Vector center = config.getHep3Vector("calorimeter.calorimeterCenter", default);
In this case, SimpleConfig will look for a parameter declared as
vector<double> calorimeter.calorimeterCenter = ... ; // Must have exactly three entries.
If the parameter is found but is either the wrong type or it does not have exactly three values, SimpleConfig will throw an exception. It will then translate the std::vector into a Hep3Vector and return it. The accessor with a default value is also provided.
Printing and Usage Statistics
The SimpleConfig class has a variety of methods to print out the contents of the configuration and to print usage statistics. To print the contents of the configuration, after all includes and parameter replacements have been processed, one can use the method:
void SimpleConfig::print( std::ostream& ost, std::string tag = "") const;
This call will print each record in the configuration in a standard format; it will be printed to the ostream specified by the first argument and the tag string will be prefixed onto each line of printout. If you want whitespace between the tag and the start of the line, include it in the definition of the tag. If a parameter has been superceded, only the final value will be printed.
The following call will print the full contents of the configuration, including any records that have been superceded; it is mostly interesting for debugging.
void SimpleConfig::printFullImage( std::ostream& ost) const;
The following call will print a summary of the number of records of each type and the number of values contained in those records. For example, a record that is a vector of length 3 counts 1 towards the number of records and 3 towards the number of values. As before, and in the following, the tag is prefixed to all printout made by this call.
void SimpleConfig::printStatisticsByType ( std::ostream& ost, std::string tag="" ) const;
The following will print the name of each record along with the number of times, so far in the job, that any code asked for its value:
void SimpleConfig::printAccessCounts( std::ostream& ost, std::string const& tag) const;
The following will print the names of all SimpleConfig parameters that have not been accessed so far in the job:
void SimpleConfig::printNeverAccessed( std::ostream& out, std::string const& tag = "" ) const;
The following will print the names of all parameters that have been accessed multiple times:
void SimpleConfig::printAccessedMultiple( std::ostream& ost, std::string const& tag) const;
The following will print the names of all parameters that were requested but were not present in the file and for which the user supplied default value was taken:
void SimpleConfig::printDefaultCounts( std::ostream& ost, std::string const& tag) const;
Depending on the value of the verbosity argument this call with either print nothing, will call all of the previous 4 methods, or somewhere in between.
void SimpleConfig::printAllSummaries( std::ostream& ost, int verbosity, std::string const& tag) const;
As of July 2012, this method was defined as:
void SimpleConfig::printAllSummaries( std::ostream& ost, int verbosity, std::string const& tag) const{ cout << "Verbosity: " << verbosity << endl; if ( verbosity <= 0 ) return; printStatisticsByType(cout, tag); if ( verbosity >= 2 ){ printAccessCounts ( cout, tag ); } if ( verbosity >= 3 ){ printNeverAccessed ( cout, tag ); printAccessedMultiple ( cout, tag ); printDefaultCounts ( cout, tag ); } }
Use in Mu2e Services and EventGenerators
Mu2e uses SimpleConfig in five places, GeometryService, ConditionsService, GlobalContantsService and two event generator modules, EventGenerator and G4BeamlineGenerator. All of these have five FHiCL parameter set arguments that control the behaviour of the printout from their SimpleConfig objects.
For example, the full set of FHiCL parameters for the GeometryService is:
GeometryService : { inputFile : "Mu2eG4/test/geom_01.txt" allowReplacement : true messageOnReplacement : false messageOnDefault : false printConfig : false configStatsVerbosity : 0 }
All of the last 5 parameters have default values and need not be specified. The default values are those shown; that is, the default is to make no printout unless there are errors. The parameters allowReplacement, messageOnReplacement and messageOnDefault are passed to the SimpleConfig object as constructor arguments.
If printConfig is true, then GeometryService will call the following immediately after successful construction of the SimpleConfig object, named _config:
_config.print(std::cout,"Geom: ")
At the end of the job, GeometryService calls the following to printout statistics on the use of paramters:
_config.printAllSummaries(std::cout, _configStatsVerbosity, "Geom: ")
The variable _configStatsVerbosity is initialized by the value from the parameter set.
The other services and modules have parameter set variables with the same names and the same behaviour. The tag values that they use to mark their output are: "Conditions: ", "GlobalConstants: ", "EvtGen: ", and "G4blGen: ".