-
Whitney Armstrong authored
modified: part1/overview.md
Whitney Armstrong authoredmodified: part1/overview.md
Tutorial Part 1
- Introduction
- How to build a detector from scratch
- The Readout and Bit Fields
- Simple Reconstruction Overview of scripts
Introduction
To a newcomer it might not be immediately clear why a generic detector
library based on dd4hep
is a good solution to the 'simulation and
reconstruction geometry problem'. However, with the following examples we
intend to show how generic detector libraries can be used to access the
geometry information in simulation and reconstruction.
We try to emphasize in the following examples that dd4hep (plus a data model) allows the software components to be only loosely decoupled. This allows for a 'no framework' framework approach where the emphasis is on the underlying algorithms used (or being designed). Furthermore, using ROOT's TDataframe, the details of each task are made manifest.
Furthermore, the 'no framework' framework allows for the each step to be executed using any tool available, however, the IO and data model should be fixed.
How to build a detector from scratch
Building a new (generic) detector using dd4hep is rather straight forward if we make the following assumptions.
- We will use the built-in sensitive detectors
- We will use the built-in data model (hits) associated with these detectors
These items can be customized by using the DD4hep plugin mechanism. This will be covered in another tutorial.
Compiling a new detector
For this tutorial we will build a simplified Roman Pot detector.
We will discuss the detector built in the source file
src/GenericDetectors/src/SimpleRomanPot_geo.cpp
.
To compile this detector into the GenericDetectors library the detector needs
to be added to the list of sources in the cmake file
src/GenericDetectors/CMakeLists.txt
.
dd4hep_add_plugin(${a_lib_name} SOURCES
src/BeamPipe_geo.cpp
...
src/SimpleRomanPot_geo.cpp # add this line
)
Building the geometry
The work of defining the detector is done in a function (here called
build_detector
) that is registered using the DD4hep plugin macro
DECLARE_DETELEMENT
.
static Ref_t build_detector(Detector& dtor, xml_h e, SensitiveDetector sens)
{
xml_det_t x_det = e;
Material air = dtor.air();
...
}
DECLARE_DETELEMENT(SimpleRomanPot, build_detector)
The argument signature of the build_detector
is:
-
Detector& dtor
: This handle provides the main hook to the detector tree (dd4hep::Detector
). -
xml_h e
: Handle to the XML<detector>
tag associated with the detector in the "compact" detector description file (more on this later). This provides the mechanism for feeding in the run-time construction parameters. -
SensitiveDetector sens
: The sensitive detector to be assigned to the sensitive volumes/elements of the detector.
The DD4hep plugin macro DECLARE_DETELEMENT(SimpleRomanPot, build_detector)
stamps out the necessary boiler plate code to register a new detector called
SimpleRomanPot
which is build by calling build_detector
.
Compact detector description entry element
The <detector>
tag defines a new instance of a detector and requires the
attributes "id", "name", and "type". For example:
<detector id="1" name="MyRomanPot" type="SimpleRomanPot"
vis="RedVis" readout="RomanPotHits" zoffset="1.0*m">
</detector>
This defines an instance of the detector named "MyRomanPot" of type
"SimpleRomanPot" (i.e. the type-name given in the first argument of the DD4hep
DECLARE_DETELEMENT
macro) and with id=1. The additional attributes (vis,
readout, zoffset) will are optional.
The detector tag is provided as the second argument in the build_detector
function. It can be parsed how ever you want in order to extract detector
information. The allowed attributes are listed in
UnicodeValues.h
where it is clear how to add new attributes.
Geometry Construction
If you are familiar with Geant4 or TGeo geometry construction then this will be easy. DD4hep has TGeo under hood but there are a few naming tweaks. The following table will help orient the user.
DD4hep | Geant4 | *TGeo |
---|---|---|
Solid | G4VSolid | TGeoShape |
Volume | G4LogicalVolume | TGeoVolume |
PlacedVolume | G4PVPlacement | TGeoNode |
Element | G4Element | TGeoElement |
Material | G4Material | TGeoMaterial/TGeoMedium |
XML Parsing Tip : Provide good default values
If you have a detector parameter which we later will tweak (while optimizing the design) try to get the value from the xml element but provide a good default value. For example:
double radius = ( x_det.hasAttr(_Unicode(radius)) ) ? x_det.attr<double>(_Unicode(radius)) : 5.0*dd4hep::cm;
This provides a default radius of 5 cm when x_det
does not have a "radius"
attribute defined. We will return to this later.
Critical parts of build_detector
We will now look at parts of the source file src/GenericDetectors/src/SimpleRomanPot_geo.cpp
.
static Ref_t build_detector(Detector& dtor, xml_h e, SensitiveDetector sens)
{
xml_det_t x_det = e;
Material air = dtor.air();
Material carbon = dtor.material("CarbonFiber");
Material silicon = dtor.material("SiliconOxide");
Material aluminum = dtor.material("Aluminum");
Material vacuum = dtor.material("Vacuum");
Material supp_mat = carbon;
Material sens_mat = silicon;
int det_id = x_det.id(); // id=1
string det_name = x_det.nameStr(); // "MyRomanPot"
Here we are grabbing the materials that are assumed to be already defined. Also
we are getting the detector id and name defined in the detector
tag.
Next we define an Assembly
volume.
Here we also stumble upon the important class
dd4hep::DetElement
.
It is a means of providing the detector hierarchy/tree, but doesn't necessarily
have to map exactly to detector geometry. However, it typically will typically
parallel the geometry (and probably should).
string module_name = "RomanPot";
Assembly assembly(det_name + "_assembly");
DetElement sdet( det_name, det_id);
sens.setType("tracker");
The last line sets the SensitiveDetector sens
argument to be the tracker type
(which is a DD4hep built-in). We will soon assign this to sensitive volumes.
sdet
is associated with the mother detector element by the constructor which
looks up the detector name (here "MyRomanPot").
double z_offset = (x_det.hasAttr(_Unicode(zoffset))) ? x_det.attr<double>(_Unicode(zoffset)) : 0.0;
double thickness = (x_det.hasAttr(_Unicode(thickness))) ? x_det.attr<double>(_Unicode(thickness)) : 0.01*dd4hep::cm;
Here we grab attributes and provide default values. We continue with default values that could also be define through attributes, however, we will want to add child elements of the detector tag (so the attributes does not grow too long).
double rp_chamber_thickness = 5.0*dd4hep::mm;
double rp_chamber_radius = 5.0*dd4hep::cm;
double rp_chamber_length = 50.0*dd4hep::cm;
Tube rp_beam_pipe_tube(rp_chamber_radius, rp_chamber_radius+rp_chamber_thickness, rp_chamber_length/2.0);
Tube rp_beam_vacuum_tube(0.0, rp_chamber_radius+rp_chamber_thickness, rp_chamber_length/2.0);
Tube rp_beam_vacuum_tube2(0.0, rp_chamber_radius, rp_chamber_length/2.0);
double rp_detector_tube_radius = 2.5*dd4hep::cm;
double rp_detector_tube_length = 20.0*dd4hep::cm;
Tube rp_detector_tube(rp_detector_tube_radius, rp_detector_tube_radius+rp_chamber_thickness, rp_detector_tube_length/2.0);
Tube rp_detector_vacuum_tube(0.0, rp_detector_tube_radius+rp_chamber_thickness, rp_detector_tube_length/2.0);
Tube rp_detector_vacuum_tube2(0.0, rp_detector_tube_radius, rp_detector_tube_length/2.0);
ROOT::Math::Rotation3D rot_X( ROOT::Math::RotationX(M_PI/2.0) );
ROOT::Math::Rotation3D rot_Y( ROOT::Math::RotationY(M_PI/2.0) );
UnionSolid rp_chamber_tee1(rp_beam_vacuum_tube, rp_detector_vacuum_tube, rot_X);
UnionSolid rp_chamber_tee12(rp_chamber_tee1, rp_detector_vacuum_tube, rot_Y);
UnionSolid rp_chamber_tee2(rp_beam_vacuum_tube2, rp_detector_vacuum_tube2, rot_X);
UnionSolid rp_chamber_tee22(rp_chamber_tee2, rp_detector_vacuum_tube2, rot_Y);
SubtractionSolid sub1(rp_chamber_tee12,rp_chamber_tee22);
Volume rp_chamber_vol("rp_chamber_walls_vol", sub1, aluminum);
Volume rp_vacuum_vol("rp_chamber_vacuum_vol", rp_chamber_tee22, vacuum);
The above code builds the up the two solids associated with 3 tubes intersecting. One volume is the aluminum vacuum chamber walls and the other is the vacuum contained within this envelope.
Next we must place these two volumes in the assembly volume (which is just an empty container-like volume. The PlacedVolume is then associated with a BitFieldValue in the readout's BitField64 readout string. In this case the "layer" BitFieldValue. The BitField64 is used to construct unique VolumeIDs and CellIDs for PlacedVolumes and Segmentations respectively.
PlacedVolume pv;
pv = assembly.placeVolume( rp_chamber_vol );
pv = assembly.placeVolume( rp_vacuum_vol );
pv.addPhysVolID( "layer", 2 );
Set the PlacedVolume BitFieldValue ID. "2" in this case.
double supp_x_half = 1.0*dd4hep::cm;
double supp_y_half = 1.0*dd4hep::cm;
double supp_thickness = 1.0*dd4hep::mm;
double supp_gap_half_width = 1.0*dd4hep::mm;
double sens_thickness = 0.1*dd4hep::mm;
Box supp_box( supp_x_half, supp_y_half, supp_thickness/2.0 );
Box sens_box( supp_x_half-supp_gap_half_width, supp_y_half-supp_gap_half_width, sens_thickness/2.0 );
Next we define vectors which are used to define a "surface" (which will later generate simulation tracker hits).
// create a measurement plane for the tracking surface attched to the sensitive volume
Vector3D u( 1. , 0. , 0. ) ;
Vector3D v( 0. , 1. , 0. ) ;
Vector3D n( 0. , 0. , 1. ) ;
//Vector3D o( 0. , 0. , 0. ) ;
// compute the inner and outer thicknesses that need to be assigned to the tracking surface
// depending on wether the support is above or below the sensor
// The tracking surface is used in reconstruction. It provides material thickness
// and radation lengths needed for various algorithms, routines, etc.
double inner_thickness = supp_thickness/2.0;
double outer_thickness = supp_thickness/2.0;
double z_shift = 5.0*dd4hep::mm;
double xy_shift = 15.0*dd4hep::mm;
SurfaceType type( SurfaceType::Sensitive ) ;
We now define a simple rectangular pixel sensor. This will be the first of four: two will come in along the x axis and two along the y axis.
// ------------- x1
Volume support1_vol( "xsenseor_supp", supp_box, supp_mat );
Volume sensor1_vol( "xsenseor_sens", sens_box, sens_mat );
VolPlane surf1( sensor1_vol, type, inner_thickness , outer_thickness, u,v,n);
sensor1_vol.setSensitiveDetector(sens);