$Date: 2003-05-16 10:18:24 $
$Revision: 1.1.1.1 $
Editors: C. Arnault, RD Schaffer
The software notes: ATL-SOFT-2001-004 and ATL-MUON-2001-014 describe the offline identifier scheme for the various detectors, where the latter note is just for the muons. The identifiers are hierarchically structured following a logical decomposition of the ATLAS detector. For example, the top level specifies the detector system (InnerDetector, LArCalorimeter, TileCalorimeter, MuonSpectrometer) and the next level down typically specifies the detector subsystem (pixel, sct, etc.). Subsequently there may be a level which distinguishes between barrel or endcap, or a level which specifies which layer or defines the eta/phi numbering within a layer. The muon system scheme is somewhat different in that they first identify the stations of the muon system and then the different detector technology within each station.
This scheme is currently being used to identify individual readout channels and as well corresponding "detector elements", where a detector element is a higher level grouping of a number of readout channels. For example, the silicon wafers server as "detector elements" for the pixels and SCT.
The readout and detector element identifiers are being used in a variety of ways. For example, the High Level Trigger is accessing data limited to regions of interest by geometrical eta/phi regions into lists of detector element identifiers which specify which bytestream data to decode and make available. Another important use is to connect the readout data its their corresponding detector description information, for example, to calculate positions or energies.
The software infrastructure to support this work can be split into three parts:
- Specification of the identifier structure
- Creation and interpretation of the identifiers
- The identifier classes themselves
Identifier structure
The identifier specification is captured by an "identifier dictionary" which is in the IdDict package. The values allowed for the identifiers of each subsystem can be expressed in terms of a set of "regions" where for each region one provides the allowed values for each of the levels. For example, for the pixel detector, the channel numbering on each wafer is the same. The number of wafers varies in each layer in the barrel, but the endcap disks are all similar. Thus, one is able to describe the pixel identifier numbering with only four regions: one for each of the three layers in the barrel and one for the endcaps.
Today, the region description is specified in XML (eXtended Markup Language) in an ASCII file. The dictionary reads the XML descriptions and maintains them in memory. There is a dictionary manager (IdDictMgr) which holds a dictionary for each detector system (InnerDetector, LArCalorimeter, TileCalorimeter, MuonSpectrometer). Each dictionary provides three primary functions:
- Access to its "fields", where each level in the identifier hierarchy corresponds to a single field. Thus, a client who "knows" the names of the different levels in of identifier may get their index or level number.
- Access to the allowed values for the identifiers of a particular subsystem (or region). The allowed values are captured by a MultiRange object, discussed in more detail in section (2. Range and MultiRange classes). The allowed values may correspond to the full identifiers down to the readout channel level, or one may request the allowed values for a limited number of levels, for example just for the detector elements.
- Packing and unpacking of compact identifiers. The MultiRange objects provide identifiers in an expanded form. It is the dictionary that "knows" how to compactify them.
Identifier creation and interpretation
There are helper classes for each detector subsystem which interact with the identifier dictionary to allow creation of the required identifiers as well as their corresponding interpretation (see IdHelper classes and tests). For the InnerDetector, these classes are PixelID, SCT_ID and TRT_ID which reside in the InDetIdentifier package. These classes are used to create Identifier objects, for example, when constructing a digit or a readout object by providing the helper class with the set of indices for the various levels of the identifier. One also uses the helper classes to extract from an Identifier object the information as to whether it belongs to the barrel or endcap, its layer number, etc.The helpers are intialized from the information in the dictionary. They are stored in the Athena Detector Store and are accessed via the detector description managers for each system. They provide the interface to manipulate identifiers for the rest of the software, e.g. Athena algorithms.
Identifier classes
There are three different identifier classes used in the software (which can be found in the Identifier package below DetectorDescription):
- Identifier - compact 32-bit identifier
- IdentifierHash - compact 32-bit identifier which can be used a a compact hash code for direct access
- ExpandedIdentifier - an identifier whose fields are "expanded", for example, into an internal vector, providing fast access to the value of any field.
The first two forms, Identifier and IdentifierHash, are the objects which are widely used in the software to provide the identification of readout channels and detector elements. The expanded form is primarily used by the helper classes and the dictionary.
The Identifier class contains the id values of the different levels packed into a 32-bit word. For a given subsystem, one may obtain different Identifier objects depending upon the number of levels included. For example for the pixels, an Identifier which identifies "detector elements" includes levels from "InnerDetector" down to "eta module" which corresponds to the individual wafers. Similarly for the muons, one can identify stations, MDT multilayers or layers or tubes all with different sets of Identifier objects. For any set of objects, e.g. a set up rpc readout strips, the identifiers can be used as a search key with corresponding log(N) lookup time. The interface of the Identifier class is quite simple. Its primary method is just the comparison operator (operator <) so that it behaves as a "type-safe unsigned int".
The IdentifierHash class is similar the Identifier class - it basically is a "type-safe unsigned int". However, it is used to distinguish the special where the total number N of a set of objects is known. In this case, one is able to transform a packed Identifier into a "hash" code with a value from 0 to N-1, and thus perform constant-time lookup, for example in a simple vector. In general, hash tables provide constant-time lookup, however, one must trade-off the cost of converting a key into a hash code and allow for clashes (where two different keys may convert into the same hash code and must be treated specially). For our situation, it is the "detector elements" which in general will be identified with IdentifierHash. This allows a fast lookup, for example, when using IdentifiableContainer to collect raw data objects within regons of interest, or when fetching the transformation matrices for the detector elements from the detector store.
The identifier dictionary describes the possible identifier ranges. One may describe several identifier dictionaries. Currently there is one dictionary for the top identifier level and one for each detector system.
A dictionary is identified by its name and may contain the follow three elements:
- individual identifier fields ("field") . A field corresponds to a single level in an identifier specification. For example, the InnerDetector defines the field "part" for the subsystems Pixel, SCT and TRT:
<field name="part"> <label name="Pixel" value="1" /> <label name="SCT" value="2" /> <label name="TRT" value="3" /> </field>
- The list of "labels" are the possible field values in terms of character strings and their corresponding numerical values.
- This "field specification" should only be used when the field values are individually labelled with names.
- Purely numerical fields do not need to be explicitly described as a field. In this case, the possible ranges are deduced from the defined regions (see next).
identifier regions ("region"). A region specifies a contiguous range of identifiers, where different regions should not overlap. For example, for layer 0 of the pixel barrel is defined as <region> <reference subregion="pixel_barrel" /> <range field="layer" value="0" /> <range field="phi_module" minvalue="0" maxvalue="21" wraparound="TRUE" /> <range field="eta_module" minvalue="-6" maxvalue="+6" /> <range field="phi_index" minvalue="0" maxvalue="327" /> <range field="eta_index" minvalue="0" maxvalue="191" /> </region>
- a region may be labelled with a "name" (not shown)
- a region contains the ordered list of fields with their explicit value range, where the order defines the identifier level structure
- each entry in this list may be
- a range assigned to a given field ("range"). A range can be specified as:
Note that a range may be specified to "wraparound" as is often the case of the phi coordinate.
- single value
- min/max values
- a space-separated list of values
- a reference to a subregion (see below)
- a reference to a separate identifier dictionary, for example
<region> <range field="subdet" value="InnerDetector" /> <dictionary name="InnerDetector" /> </region>defines the top level of the InnerDetector identifiers with the rest defined in the InnerDetector dictionarysubregions ("subregion"). A subregion specifies a subset of a region and is useful when a subregion is the same for a number of region specifications in a dictionary. For example, the subregion "pixel_barrel" of the above example is defined as: <subregion name="pixel_barrel"> <range field="part" value="Pixel" /> <range field="barrel_endcap" value="barrel" /> </subregion>A subregion must be identified by a name.
A Range class corresponds to the region specification of the dictionary (see idDict region). This effectively captures the a range of values for each of the identifier levels for a region or subset of identifiers. The Range class interacts with the ExpandedIdentifier class. This class can be found in the Identifier package under DetectorDescription. The Range interface provides:The MultiRange class is simply a "container" class for more than one Range. It is a MultiRange that can represent a full Identifier specification for the identifiers of a subsystem.
- a match method which can be used to check whether an ExpandedIdentifier is "valid", i.e. within the specification captured in a range object.
- iterators to be able to iterate over all ExpandedIdentifiers within the range
- a cardinality method which provides the number of identifiers within the range
- a show method with prints to std::cout the range values
- an embedded field class to examine the fields/levels of the range. The fields are accessed with operator []
- minumum and maximum methods for the bounding Identifiers of a range
For each detector system there are separate helper classes for each subsystem which allow creation and interpretation of the various kinds of identifiers. These classes are the "servers" for any client who needs to know more about an identifier than simply using it as a lookup key. These classes "pre-digest" the information in the identifier dictionary. We provide here some of the details of the helper classes developed for the InnerDetector to allow other helper classes to be developed.
4.1 - IdDictTest algorithm
There is a test algorithm IdDictTest under AtlasTest/DetDescrTest which serves as an overall test of the idhelpers. For the inner detector, tests are performed by comparing detector element identifiers which are obtained from the detector description manager (derived from the geant 3 information) with the detector elements identifiers provided by the helpers. This check assures that the sets of identifiers are identical and has been used to correct the XML specification which is the source for the idhelpers.
4.2 - PixelID example
We take as an example the PixelID class which is the helper class for the Pixel ids (in package InDetIdentifier under InnerDetector/InDetDetDescr).
- Creating Identifier objects This class constucts Identifier objects for wafers and individual pixels given the correct set of indices:
// For a single crystal Identifier wafer_id ( int barrel_ec, int layer_disk, int phi_module, int eta_module ) const; // For an individual pixel Identifier pixel_id ( int barrel_ec, int layer_disk, int phi_module, int eta_module, int phi_index, int eta_index) const; Identifier pixel_id ( const Identifier& id, int phi_index, int eta_index) const;- Id conversion Conversion between the types of identifiers is given by:
// // access to IdContext's which define which levels or fields are // contained in the Pixel id // // id for wafer IdContext wafer_context (void) const; // id for pixels IdContext pixel_context (void) const; // Create compact id from hash id (return == 0 for OK) virtual int get_id (const IdentifierHash& hash_id, Identifier& id, const IdContext* context = 0) const; // Create hash id from compact id (return == 0 for OK) virtual int get_hash (const Identifier& id, IdentifierHash& hash_id, const IdContext* context = 0) const; // Create Identifier from expanded id, which is returned by the // id_iterators int get_id (const ExpandedIdentifier& old_id, Identifier& new_id) const;Note that the IdContext objects contain the information on how many levels are included in an Identifier. This is needed because the identifiers themselves do not know how "long" they are (only an ExpandedIdentifier does). For example, a wafer id is missing the eta_channel and phi_channel fields of a pixel id, that is they are just set to 0. So when converting an Identifier to an IdentifierHash and vice-versus one must provide the IdContext.- Helper initialization Initialization of the helper is done with the method:
// Initialization from the identifier dictionary virtual int initialize_from_dictionary(const IdDictMgr& dict_mgr);Note that the IdHelpers are initialized with an IdDictMgr which is a manager that holds all of the identifier dictionaries - one for each detector system. This manager is obtain from the DetectorStore. See the InDetMgrDetDescrCnv::initIds method in the InDetMgrDetDescrCnv package under InnerDetector/InDetDetDescrCnv as an example.
The initialization itself procedures along the following steps:
- Query the dictionary to find out the index number of each field or level. Here the idhelper "knows" only the name of each level and asks the dictionary for its index.
- The PixelID initializes two MultiRange objects - one for the wafer ids and one for the pixel ids. For this one uses the following methods of the class IdDictDictionary:
MultiRange build_multirange () const; /** * Get MultiRange for a specific region_id up to and including * 'last_field' If last_field == "", all fields are * taken. Prepend prefix if provided. */ MultiRange build_multirange (const ExpandedIdentifier& region_id, const Range prefix = Range(), const std::string last_field = "") const;where the former creates a multirange for all levels, i.e. for the pixel id multirange, and the latter allows one to extract a multirange for just a subset of the levels, i.e. for the wafer multirange.- With the multiranges in hand, one can initialize a vector of IdentifierHashes. The easiest way to do this is to just create a vector of Identifier objects. Then to get a hash given an Identifier, one simple does a binary search for the Identifier in the vector and creates an IdentifierHash from the vector index of Identifier found.
- Other sorts of initialization can be done at this step which may help to provide further functionality. For example, to provide fast access to detector elements which are adjacent neighbours in eta or phi, one may want to set up tables which can be used by the IdentifierHashes to get the IdentifierHash of each neighbour +/-1 in eta or phi.
- Other methods Other useful methods are hash maximum sizes and iterators over the full range of identifiers:
// // Hash table maximum sizes // size_type wafer_hash_max (void) const; size_type pixel_hash_max (void) const; // // Iterators over full set of ids. Wafer iterator is sorted // const_id_iterator wafer_begin (void) const; const_id_iterator wafer_end (void) const; // For pixel ids, only expanded id iterators are available. Use // following "get_id) method to obtain a compact identifier const_expanded_id_iterator pixel_begin (void) const; const_expanded_id_iterator pixel_end (void) const;- Decoding Identifiers Finally, methods are needed to extract the individual fields from an Identifier, for example:
// Test for barrel bool is_barrel (const Identifier& id) const; // Values of different levels int barrel_ec (const Identifier& id) const; int layer_disk (const Identifier& id) const; int phi_module (const Identifier& id) const; int eta_module (const Identifier& id) const; int phi_index (const Identifier& id) const; int eta_index (const Identifier& id) const;