[Tutorial] GEANT4 Introduction: Data Acquisition & ROOT Basics

Weihe Zeng
Tsinghua University
Dec. 18, 2017


  • Review: Passing information from Step -> Track -> Event -> Run
  • Sensitive Detector
  • STL Containers and File I/O

    • STL containers
    • standard output / file stream (binary/formatted)
    • Analysis Tool g4tools -> csv, ROOT, XML
  • ROOT basics: histogram, tree (n-tuple), function and fitting

  • Execute a Geant4 Program: Hard-coded v.s. Macro files.

Traditional Data Flow


User Actions (passing information)

  • G4UserSteppingAction
// Get Current track information and "delta" data
virtual void UserSteppingAction(const G4Step*);  
  • G4UserTrackingAction
virtual void PreUserTrackingAction(const G4Track*);  
virtual void PostUserTrackingAction(const G4Track*);  
  • G4UserEventAction
virtual void BeginOfEventAction(const G4Event*);  
virtual void EndOfEventAction(const G4Event*);  
  • G4UserRunAction
virtual void BeginOfRunAction(const G4Run*);  
virtual void EndOfRunAction(const G4Run*);  

Sensitive Detector and Hits

Recall how we ensure that each step is within our detector:

  • By geometry position (based on World's coordinate);
  • Get volume (pointer,r name and copy#) via TouchableHandle and compare with our detector;
auto curPosi = step->GetPreStepPoint()->GetPosition();  
G4bool flagInDet = isInDetector(curPosi);  
auto volume = step->GetPreStepPoint()->GetTouchableHandle()->GetVolume();  
if ( volume == detPhysicalVolume ) {...}  

Can we define some "sensitive" geometry as "detector" during Geometry Construction?

  • User actions: full control and straightforward, but is not a ideal "OO" way;
  • Sensitive Detector (SD): useful built-in utilities, clear definition of each objects, but with more code.

Components of SD

To design a sensitive detector, users should complete two abstract classes and assign SD to targeting logical volume.

  • Hit G4VHit: a snapshot of the physical interaction of a track in the sensitive region of a detector. In it you can store information associated with a G4Step object.
    • the position and time of the step,
    • the momentum and energy of the track,
    • the energy deposition of the step,
    • geometrical information
  • SD G4VSensitiveDetector: represents a detector and constructs of hit objects using information from steps along a particle track.
  • Defining a SD for one geometry (logical volume)
    • G4VUserDetectorConstruction::ConstructSDandField
    • Each SD object must have a unique name

Hits and hit collection

class B4cCalorHit : public G4VHit{  
    B4cCalorHit(const B4cCalorHit&);
    virtual ~B4cCalorHit();
    // operators
    const B4cCalorHit& operator=(const B4cCalorHit&);
    G4int operator==(const B4cCalorHit&) const;
    inline void* operator new(size_t);
    inline void  operator delete(void*);
    // methods from base class
    virtual void Draw() {}
    virtual void Print();
    // methods to handle data
    void Add(G4double de, G4double dl);
    // get methods
    G4double GetEdep() const;
    G4double GetTrackLength() const;     
    G4double fEdep;        //Energy deposit in the sensitive volume
    G4double fTrackLength; //Track length in the sensitive volume
using B4cCalorHitsCollection = G4THitsCollection<B4cCalorHit>;  

Sensitive Detector

class B4cCalorimeterSD : public G4VSensitiveDetector{  
    B4cCalorimeterSD(const G4String& name, 
                     const G4String& hitsCollectionName, 
                     G4int nofCells);
    virtual ~B4cCalorimeterSD();  

    // methods from base class
    virtual void   Initialize(
        G4HCofThisEvent* hitCollection);
    virtual G4bool ProcessHits(
        G4Step* step, G4TouchableHistory* history);
    virtual void EndOfEvent(
        G4HCofThisEvent* hitCollection);

    B4cCalorHitsCollection* fHitsCollection;
    G4int  fNofCells;

void B4cCalorimeterSD::Initialize(G4HCofThisEvent* hce){  
  // Create hits collection
  fHitsCollection = new B4cCalorHitsCollection(
      SensitiveDetectorName, collectionName[0]); 

  // Add this collection in hce
  auto hcID = G4SDManager::GetSDMpointer()
  hce->AddHitsCollection( hcID, fHitsCollection ); 

  // Create hits
  for (G4int i=0; i<fNofCells+1; i++ ) 
    fHitsCollection->insert(new B4cCalorHit()); 

G4bool B4cCalorimeterSD::ProcessHits(  
   G4Step* step, G4TouchableHistory*){  
  // energy deposit
  auto edep = step->GetTotalEnergyDeposit();  
  // step length
  G4double stepLength = step->GetStepLength();
  // Get calorimeter cell id 
  auto touchable = step->GetPreStepPoint()->GetTouchable();    
  auto layerNumber = touchable->GetReplicaNumber(1);

  // Get hit accounting data for this cell
  auto hit = (*fHitsCollection)[layerNumber];
  // Get hit for total accounting
  auto hitTotal 
    = (*fHitsCollection)[fHitsCollection->entries()-1];
  // Add values
  hit->Add(edep, stepLength);
  hitTotal->Add(edep, stepLength);       
  return true;

Associate to logical volume

void B4cDetectorConstruction::ConstructSDandField(){  
  // Sensitive detectors
  auto absoSD 
    = new B4cCalorimeterSD("AbsorberSD", 
        "AbsorberHitsCollection", fNofLayers);

  auto gapSD 
    = new B4cCalorimeterSD("GapSD", 
        "GapHitsCollection", fNofLayers);
// method of G4VUserDetectorConstruction
void G4VUserDetectorConstruction::SetSensitiveDetector  
   (const G4String& logVolName, 
    G4VSensitiveDetector* aSD, 
    G4bool multi=false)

Alternative way (NOTE: absorberLV and gapLV should be declared as private member of B4cDetectorConstruction)

// using method of G4VLogicalVolume

Process data in G4UserEventAction

void B4cEventAction::EndOfEventAction(const G4Event* event){  
  // Get hits collections IDs (only once)
  if ( fAbsHCID == -1 ) {
      = G4SDManager::GetSDMpointer()->GetCollectionID("AbsorberHitsCollection");
      = G4SDManager::GetSDMpointer()->GetCollectionID("GapHitsCollection");  }
  // Get hits collections
  auto absoHC = GetHitsCollection(fAbsHCID, event);
  auto gapHC = GetHitsCollection(fGapHCID, event);
  // Get hit with total values
  auto absoHit = (*absoHC)[absoHC->entries()-1];
  auto gapHit = (*gapHC)[gapHC->entries()-1]; 

  // Print per event (modulo n)
  auto eventID = event->GetEventID();
  auto printModulo = G4RunManager::GetRunManager()->GetPrintProgress();
  if ( ( printModulo > 0 ) && ( eventID % printModulo == 0 ) ) {
    G4cout << "---> End of event: " << eventID << G4endl;
      absoHit->GetEdep(), absoHit->GetTrackLength(),
      gapHit->GetEdep(), gapHit->GetTrackLength());

Scorer (pre-defined SD) in GEANT4


void B4dDetectorConstruction::ConstructSDandField(){  
  // declare Absorber as a MultiFunctionalDetector scorer
  auto absDetector = new G4MultiFunctionalDetector("Absorber");

  G4VPrimitiveScorer* primitive;
  primitive = new G4PSEnergyDeposit("Edep");

  primitive = new G4PSTrackLength("TrackLength");
  auto charged = new G4SDChargedFilter("chargedFilter");
  primitive ->SetFilter(charged);


Data Container in C++

Standard Template Library (STL) in C++ provides many useful containers that store data.

Sequence containers

Sequence containers implement data structures which can be accessed sequentially.

| name | description | | ---------------------------------------- | ---------------------------------------- | | array(C++11) | static contiguous array (class template) | | vector | dynamic contiguous array (class template) | | deque | double-ended queue (class template) | | forward_list(C++11) | singly-linked list (class template) | | list | doubly-linked list (class template) |

Associative containers

Associative containers implement sorted data structures that can be quickly searched (O(log n) complexity).

| name | description | | ---------------------------------------- | ---------------------------------------- | | set | collection of unique keys, sorted by keys (class template) | | map | collection of key-value pairs, sorted by keys, keys are unique (class template) | | multiset | collection of keys, sorted by keys (class template) | | multimap | collection of key-value pairs, sorted by keys (class template) |

Unordered associative containers

Unordered associative containers implement unsorted (hashed) data structures that can be quickly searched (O(1)amortized, O(n) worst-case complexity).

| name | description | | ---------------------------------------- | ---------------------------------------- | | unorderedset(C++11) | collection of unique keys, hashed by keys | | unorderedmap(C++11) | collection of key-value pairs, hashed by keys, keys are unique (class template) | | unorderedmultiset(C++11) | collection of keys, hashed by keys (class template) | | unorderedmultimap(C++11) | collection of key-value pairs, hashed by keys (class template) |

Container adaptors

Container adaptors provide a different interface for sequential containers.

| name | description | | ---------------------------------------- | ---------------------------------------- | | stack | adapts a container to provide stack (LIFO data structure) (class template) | | queue | adapts a container to provide queue (FIFO data structure) (class template) | | priority_queue | adapts a container to provide priority queue (class template) |

Example: vector usage

#include <iostream>
#include <vector>
// Create a vector containing integers
std::vector<int> v = {7, 5, 16, 8};  
// Add two more integers to vector

// Iteration Method 1
for(int n : v)  
  std::cout << n << '\n';

// Iteration Method 2
for (std::vector<int>::iterator it = v.begin();  
     it != v.end(); ++it)
  std::cout << *it << '\n';

// Iteration Method 3
for (std::vector<int>::size_type i = 0; i < v.size(); i++)  
  std::cout << v[i] << '\n'; // or v.at(i)

File I/O


Example: ofstream and ifstream (binary/formatted)

#include <iostream>
#include <fstream>
#include <string>

int main() {  
  std::string filename = "Test.b";
    // open mode: default std::ios::out
    std::ofstream ostrm(filename, std::ios::binary);
    double d = 3.14;
    // binary output
    ostrm.write(reinterpret_cast<char*>(&d), sizeof d); 
    // text output
    ostrm << 123 << "abc" << '\n';                      
  // read back
  std::ifstream istrm(filename, std::ios::binary);
  double d;
  istrm.read(reinterpret_cast<char*>(&d), sizeof d);
  int n;
  std::string s;
  istrm >> n >> s;
  std::cout << " read back: " << d << " " << n << " " << s << '\n';

Analysis Tools

g4tools provides code to write and read histograms and ntuples in several formats: ROOT, XML AIDA format and CSV (comma-separated values format).

The analysis classes provide a uniform, user-friendly interface to g4tools and hide the differences according to a selected output technology from the user.


Public interface: G4AnalysisManager

Create or get instance of G4AnalysisManager:

auto analysisManager = G4AnalysisManager::Instance();  

Select output file format (in a separate header, e.g. B4Analysis.hh):

#ifndef B4Analysis_h
#define B4Analysis_h 1

//#include "g4root.hh"
//#include "g4xml.hh"
#include "g4csv.hh"

xml and csv AnalysisManager Class doesn't include SetNtupleMerging method, which should be comment out in B4RunAction.

File handling (in UserRunAction)

#include "B4Analysis.hh"

void B4RunAction::BeginOfRunAction(const G4Run* run)  
  // Get analysis manager
  auto analysisManager = G4AnalysisManager::Instance();

  // Open an output file

void B4RunAction::EndOfRunAction(const G4Run* aRun)  
  // Save histograms
  auto analysisManager = G4AnalysisManager::Instance();

Create and fill Histograms

Basic functions:

G4int CreateH1(const G4String& name, const G4String& title,  
               G4int nbins, G4double xmin, G4double xmax,
               const G4String& unitName = "none",
               const G4String& fcnName = "none",
               const G4String& binSchemeName = "linear");

G4int CreateH1(const G4String& name, const G4String& title,  
               const std::vector<G4double>& edges,
               const G4String& unitName = "none",
               const G4String& fcnName = "none");

G4int CreateH2(const G4String& name, const G4String& title,  
               G4int nxbins, G4double xmin, G4double xmax,
               G4int nybins, G4double ymin, G4double ymax, ...);

G4bool FillH1(G4int id, G4double value,  
              G4double weight = 1.0);
G4bool FillH2(G4int id, G4double xvalue, G4double yvalue,  
              G4double weight = 1.0);

Histograms are created in Constructor of UserRunAction:

B4RunAction::B4RunAction() : G4UserRunAction(){  
  // Create analysis manager
  // ...
  // Creating histograms (1-dimensional)
  analysisManager->CreateH1("1","Edep in absorber", 
                            100, 0., 800*MeV);
  analysisManager->CreateH1("2","Edep in gap", 
                            100, 0., 100*MeV);

Usage in EndOfEventAction of UserEventAction:

void B4aEventAction::EndOfEventAction(const G4Run* aRun){  
  // Fill histograms
  auto analysisManager = G4AnalysisManager::Instance();
  // FillH1(G4int id, G4double value, G4double weight = 1.0);
  analysisManager->FillH1(1, fEnergyAbs);
  analysisManager->FillH1(2, fEnergyGap);

Create and Fill Ntuples

G4int CreateNtuple(const G4String& name,  
                   const G4String& title);
// Create columns in the last created ntuple
G4int CreateNtupleXColumn(const G4String& name);  
void  FinishNtuple();

// Create columns in the ntuple with given id
G4int CreateNtupleXColumn(G4int ntupleId,  
                          const G4String& name);
void  FinishNtuple(G4int ntupleId);

// Methods for ntuple id = FirstNtupleId
G4bool FillNtupleXColumn(G4int id, G4Xtype value);  
G4bool AddNtupleRow();

// Methods for ntuple id > FirstNtupleId (more ntuples exist)
G4bool FillNtupleIColumn(G4int ntupleId, G4int columnId,  
                         Xtype value);
G4bool AddNtupleRow(G4int ntupleId);  

[X, Xtype] can be [I, G4int], [F, G4float], [D, G4double] or [S, G4String]

Ntupless are created in Constructor of UserRunAction:

B4RunAction::B4RunAction() : G4UserRunAction(){  
  // Create analysis manager and histograms
  // ...  
  // Creating ntuple
  analysisManager->CreateNtuple("B4", "Edep and TrackL");

Usage in EndOfEventAction of UserEventAction:

void B4aEventAction::EndOfEventAction(const G4Run* aRun){  
  auto analysisManager = G4AnalysisManager::Instance();
  // fill ntuple
  analysisManager->FillNtupleDColumn(0, fEdep);
  analysisManager->FillNtupleDColumn(2, fLength);

ROOT Basics


void poissonHist(){  
  auto cnt_r_h=new TH1F("count_rate",
              "Count Rate;N_{Counts};# occurencies",
              100, // Number of Bins
              -0.5, // Lower X Boundary
              15.5); // Upper X Boundary
  TRandom3 rndgen;
  // simulate the measurements
  for (int imeas=0;imeas<400;imeas++)
  // draw histogram
  auto c= new TCanvas();
  // Print summary
  cout << "Moments of Distribution:\n"
       << " - Mean     = " << cnt_r_h->GetMean() << " +- "
                           << cnt_r_h->GetMeanError() << "\n"
       << " - Std Dev  = " << cnt_r_h->GetStdDev() << " +- "
                           << cnt_r_h->GetStdDevError() << "\n";


tree (ntuple)

void write_ntuple_to_file(  
  const std::string& outputFileName="conductivity_experiment.root";
  unsigned int numDataPoints=1000000);
  TFile ofile(outputFileName.c_str(),"RECREATE");
  // Initialise the TNtuple
  TTree cond_data("cond_data", "Example N-Tuple");
  // define the variables and book them for the ntuple
  float pot,cur,temp,pres;
  cond_data.Branch("Potential", &pot, "Potential/F");
  cond_data.Branch("Current", &cur, "Current/F");
  cond_data.Branch("Temperature", &temp, "Temperature/F");
  cond_data.Branch("Pressure", &pres, "Pressure/F");
  for (int i=0;i<numDataPoints;++i){
    // Fill it randomly to fake the acquired data
    pot = gRandom->Uniform(0.,10.)*gRandom->Gaus(1.,0.01);
    temp = gRandom->Uniform(250.,350.)+gRandom->Gaus(0.,0.3);
    pres = gRandom->Uniform(0.5,1.5)*gRandom->Gaus(1.,0.02);
    cur = pot/(10.+0.05*(temp-300.)-0.2*(pres-1.))*gRandom->Gaus(1.,0.01);
  // Save the ntuple and close the file

function and fitting

double the_gausppar(double* vars, double* pars){  
    return pars[0]*TMath::Gaus(vars[0],pars[1],pars[2])+

int functionAndFitting(){  
    TF1 parabola("parabola","[0]+[1]*x+[2]*x**2",0,20);
    TF1 gaussian("gaussian","[0]*TMath::Gaus(x,[1],[2])",0,20);
    TF1 gausppar("gausppar",the_gausppar,-0,20,6);
    double a=15; double b=-1.2; double c=.03;
    double norm=4; double mean=7; double sigma=1;

    TH1F histo("histo","Signal plus background;X vals;Y Vals",50,0,20);
    // Fake the data
    for (int i=1;i<=5000;++i) 

    // Reset the parameters before the fit and set
    int npar=gausppar.GetNpar();
    for (int ipar=2;ipar<npar;++ipar) gausppar.SetParameter(ipar,1);

    // perform fit ...
    auto fitResPtr = histo.Fit(&gausppar, "S");
    // ... and retrieve fit results
    fitResPtr->Print(); // print fit results
    // get covariance Matrix an print it
    TMatrixDSym covMatrix (fitResPtr->GetCovarianceMatrix());

    // Set the values of the gaussian and parabola
    for (int ipar=0;ipar<3;ipar++){
      // draw results
    parabola.DrawClone("Same"); gaussian.DrawClone("Same");
    return 0;


Execute a GEANT4 Program


int main(){  
  // Construct the default run manager
  G4RunManager* runManager = new G4RunManager;
  // Set mandatory initialization classes
  runManager->SetUserInitialization(new B1DetectorConstruction);
  runManager->SetUserInitialization(new QBBC);
  runManager->SetUserAction(new B1PrimaryGeneratorAction);
  // Set user action classes
  runManager->SetUserAction(new B1SteppingAction());
  runManager->SetUserAction(new B1EventAction());
  runManager->SetUserAction(new B1RunAction());

  // Initialize G4 kernel
  // start a run
  int numberOfEvent = 1000;
  // job termination
  delete runManager;
  return 0;

Macro file

int main(int argc,char** argv){  
  // Construct the default run manager
  G4RunManager* runManager = new G4RunManager;
  // Set mandatory initialization classes
  // Set user action classes
  // Initialize G4 kernel
  //read a macro file of commands
  G4UImanager* UI = G4UImanager::GetUIpointer();
  G4String command = "/control/execute ";
  G4String fileName = argv[1];

  // job termination
  delete runManager;
  return 0;

In command line, run it with your macro file:

./exampleB1 run_marco.mac

Interactive Mode

int main(int argc,char** argv){  
  // Construct the default run manager
  G4RunManager* runManager = new G4RunManager;
  // Set mandatory initialization classes
  // Set user action classes
  // Initialize G4 kernel
  // Define UI terminal for interactive mode
  G4UIsession * session = new G4UIterminal;
  delete session;
  // job termination
  delete runManager;
  return 0;

In the prompt G4 kernel:

Idle> /run/beamOn 1000  

General Case

int main(int argc,char** argv){  
  // Construct the default run manager
  G4RunManager* runManager = new G4RunManager;
  // Set mandatory initialization classes
  // Set user action classes
  // Initialize G4 kernel

#ifdef G4VIS_USE
  // Initialize visualization
  G4VisManager* visManager = new G4VisExecutive;

  // Get the pointer to the User Interface manager
  G4UImanager* UImanager = G4UImanager::GetUIpointer();

  if (argc!=1) {
    // batch mode
    G4String command = "/control/execute ";
    G4String fileName = argv[1];
  } else {
    // interactive mode : define UI session
#ifdef G4UI_USE
    G4UIExecutive* ui = new G4UIExecutive(argc, argv);
#ifdef G4VIS_USE
    UImanager->ApplyCommand("/control/execute init_vis.mac");
    UImanager->ApplyCommand("/control/execute init.mac");
    delete ui;
#ifdef G4VIS_USE
  delete visManager;
  delete runManager;


Modify the original B1 example to record the following information in detector fScoringVolume:

  • Total energy deposition in EACH EVENT;
  • Total tracking length of CHARGED particles in EACH EVENT;
  • Energy spectrum (histogram) of this simulation run: 0-6 Mev, 1024 bins.

Run exampleB1 with this macro file:

/control/verbose 2
/run/verbose 2
/run/printProgress 1000
/run/beamOn 10000


  • step->GetTrack()->GetDefinition()->GetPDGCharge() to get charge of particle;
  • step->GetTotalEnergyDeposit() and step->GetStepLength() to get energy and step length.

Output: data in your favorite format (recommend using G4AnalysisManager).