Project

General

Profile

Art G4 Utility Functions

Including in your code

There are some simulation and geometry tasks that you do a lot and are tedious to code. The artg4/util directory has some utility functions that you may find useful. In order to use these functions, make sure your source code has

#include "artg4/util/util.hh" 

and that the CMakeLists.txt file for your source directory has, in the art_make call, "artg4_util" after the LIBRARIES option so that you'll link with the right library. If there is no LIBRARIES option, then add it -- for example art_make(LIBRARIES "artg4_util") .

Getting Sensitive Detectors

Many of the detector services will need to assign a sensitive detector to their logical volumes. In g2migtrace, one instance of each sensitive detector was used to produce time-ordered hits. We will use the same scheme with a slightly different implementation.

The basic idea is to call a function that will figure out if the sensitive detector has already been created. It either returns a pointer to that SD, or creates a new one and returns a pointer to that. The way we do it is by using a templated function call.
Here is how it would look if I was trying to get the MyDetectorSD as part of the MyDetector_service.hh :

 #include "MyDetectorSD.hh" 
 namespace myNamespace { 
   class MyDetector::MyDetector{
      MyDetector();
     ~MyDetector();

    private: 
       G4String myDetectorSDname_;
       MyDetectorSD* myDetectorSD_;
  };
}

Then in the MyDetector_service.cc file, the constructor would be implemented as follows:

 #include "MyDetector_service.hh" 
 #include "artg4/util/util.hh" 
myNamespace::MyDetector::MyDetector() :
   myDetectorSDname_("MyDetector"),
   myDetectorSD_(0)  // Initialize to 0 and assign below
{
 myDetectorSD_ = artg4::getSensitiveDetector<MyDetectorSD>(myDetectorSDname_);
}

The way this works is that the compiler substitutes MyDetectorSD into the template and checks that it derives from G4VSensitiveDetector. You have to use the angle brackets after the function name so the compiler knows what type to substitute. If you try to use this template with a class that does not derive from G4VSensitiveDetector, you will get a compilation error. So if you try to do something like this:

G4String x;
x = artg4::getSensitiveDetector<G4String>("myName");

You get an error like this:
/Users/kiburg/port/srcs/gm2ringsim/inflector/Inflector_service.cc:135:62: error: no matching function for call to 'getSensitiveDetector(const char [7])'
/Users/kiburg/port/srcs/gm2ringsim/inflector/Inflector_service.cc:135:62: note: candidate is:
In file included from /Users/kiburg/port/srcs/gm2ringsim/inflector/Inflector_service.cc:31:0:
/Users/kiburg/port/srcs/artg4/util/util.hh:29:3: note: template<class T> typename std::enable_if<std::is_base_of<G4VSensitiveDetector, T>::value, T*>::type artg4::getSensitiveDetector(G4String)
/Users/kiburg/port/srcs/artg4/util/util.hh:29:3: note:   template argument deduction/substitution failed:

The line "template argument deduction/substitution failed" is the hint that the template cannot generate a function for the class you passed in.

Setting Visual Attributes

In g2migtrace, visual attributes for Geant volumes were hard-coded. In artg4, you an put them in the geometry FCL for your detector. See Geometry_ for more information. For example, you can have in your FCL

vac_geom : {
  // ...
  display : true
  vacColor : [1.0, 0.0, 0.0, 0.0]
}

Color vectors must be four elements long (Red, Green, Blue, Opacity). For Opacity, 1.0 means completely opaque and 0.0 means completely transparent. The display variable says whether or not to show the detector in the display.

Your C++ code would then have things like,

   MyGeometry g;  // Get my geometry somehow 

   G4LogicalVolume* wallLV = ... // Make logical volume

    // Set visualization attributes
    G4VisAttributes* att;
    if ( g.displayWall ) {
      att = new G4VisAttributes(
                                G4Colour(g.wallColor[0], g.wallColor[1],
                                         g.wallColor[2], g.wallColor[3] ) ) ;
      att -> SetForceWireframe(1);
    }
    else{
      att = new G4VisAttributes(0);
    }

     wallLV -> SetVisAttributes(att);

If you have a lot of detectors, that gets tedious to write and it clutters your code.

So to make things easier, util has functions called setVisAtts (from artg4/util/util.hh)...

namespace artg4 {
  void setVisAtts(G4LogicalVolume* lv, bool display, const std::vector<double> & rgba);
  void setVisAtts(G4LogicalVolume* lv, bool display, const std::vector<double> & rgba, std::function<void (G4VisAttributes*)> func );
}

Note that these are free functions in the artg4 namespace.

To turn on/off the display of the detector and set the color if the display is turned on, then in your construction code you would do (this would replace all of the code above)...

MyGeometry g;  // Get my geometry somehow
G4LogicalVolume* myLV = ... // Make logical volume
artg4::setVisAtts(myLV, g.display, g.color);

This assumes that g.display is the boolean which says whether or not to turn on/off display of the detector and g.color is a 4 element vector with Red, Green, Blue, Opacity values.

There are some instances, like in the wallLV example above, that you have to do extra visualization settings, like the SetForceWireframe. You can pass in a function to setVisAtts that will do that extra work. Here, we'll use a new feature of C++ 2011 called a Lambda Functions (also known as anonymous functions)...

VacGeometry g; // Get geometry somehow
G4LogicalVolume* wallLV = ... // Make the logical volume
artg4::setVisAtts( wallLV, g.displayWall, g.wallColor,
                      [] (G4VisAttributes* att) {
                              att->SetForceWireframe(1);
                      }
);

That strange looking construction with the [] in front is the lambda function. It is essentially writing a little function in the argument list of the main function call. Here, this lambda function wants a pointer to G4VisAttributes as an argument and then it sets the SetForceWireframe setting. Note that if g.displayWall were false, the lambda function would never be called. What's happening behind the scenes is that if the display boolean is true, then the G4VisAttributes object is created with the color setting. The pointer to the G4VisAttributes is then passed into your lambda function where you can make whatever changes you like.

If this SetForceWireframe value were in fact in the geometry, then you would do.

VacGeometry g; // Get geometry somehow
G4LogicalVolume* wallLV = ... // Make the logical volume
artg4::setVisAtts( wallLV, g.displayWall, g.wallColor,
                      [&g] (G4VisAttributes* att) {
                              att->SetForceWireframe(g.visWireFrame);
                      }
);

The &g within the brackets means we want to use this variable from the current scope (e.g. the enclosing calling function) and pass it in by reference. This is a special "capture" feature of lambda functions. You can't put g in the lambda function argument list (like we did for att) because when the lambda function is actually called within setVisAtts, g is not in that scope. So what we're doing here is capturing g from the scope where the lambda function is defined (not run), making what is called a "closure". Anyway, ...

If you end making the same lambda function a lot, you can even set it into a variable

auto extraVisFcn = [] (G4VisAttributes* att) { att->SetForceWireframe(1);  };

// ...

VacGeometry g; // Get geometry somehow
G4LogicalVolume* wallLV = ... // Make the logical volume
artg4::setVisAtts( wallLV, g.displayWall, g.wallColor, extraVisFcn);

Pretty cool, eh?

Adding numbers to names

We'd like to name detectors things like "VacuumWall[ 8 ]" (the spaces around the 8 are necessary to prevent Redmine from turning that into a footnote). Adding this number to the name is kind of a pain. Longhand, you would do (where arcNum has the number),

std::ostringstream o;
o << "VacuumWall[" << std::setfill('0') << std::setw(2) << arcNum << ']';
std::string name = o.str();

Typing all that in gets old fast.

So in artg4/util/util.hh there is a little helper function,

namespace artg4 {
  std::string addNumberToName(const std::string& name, int number);
}

You give it a name like "VacuumWall" and a number like 8 and it'll return "VacuumWall[ 8 ]".

std::string name = artg4::addNumberToName("VacuumWall", arcNum);

Note that name is a string. To pass that within a call to G4LogicalVolume or G4PVPlacement, you need to call the c_str() member function of std::string. E.g.

  G4LogicalVolume* vacLV = new G4LogicalVolume(
                                                 us,
                                                 artg4Materials::Vacuum(),
                                                 lvName.c_str(),
                                                 0,
                                                 0);