Sensitive Detectors#

The user can define sensitive detectors by defining a custom data structure and 3 callback functions, which will initialize, fill and dispose the defined data structure. Later, the instantiated sensitive detector would be associated to one or more logical volumes of the detector setup.

Instantiating a G4JLSensitiveDetector will require the following arguments:

  • name. A string to identify the sensitive detector. No default.

  • sd data. A instance of a user defined G4JLSDData mutable data structure that will passed to each callback invocation.

  • initialize method. User method that is called at the beginning of each event. The signature is (::B2aSDData, ::G4HCofThisEvent)::Nothing.

  • endOfEvent method. User method that is called at the end of each event. The signature is (::B2aSDData, ::G4HCofThisEvent)::Nothing.

  • processHits method. User method that is called at simulation step that ends at the associated logical volume. The signature is (::B2aSDData, ::G4Step, ::G4TouchableHistory)::Bool. Consult the G4Step reference manual to see what you can get from the G4Step. It returns true if a true hit is generated.

using Geant4
using Geant4.SystemOfUnits

Sensitive Detector Data#

Let’s define something very simple, for example collecting generated Hits (i.e deposit energies) on a sensitive detector.

#---Hit structure------------------------------------------------------------------
@enum HitType Si ScintCryst
struct Hit
    arrivalTime::Float64
    depositedEnergy::Float64
    hittype::HitType
    position::G4ThreeVector
    function Hit(time, pos, edep, typ)  # constructor
        hit = new(time, edep, typ, G4ThreeVector())
        assign(hit.position, pos)       # this is needed to fill a G4ThreeVector (in C++ = operator)
        return hit
    end
end

#--SD data structure---------------------------------------------------------------
struct CrystalSDData <: G4JLSDData
    hitcollection::Vector{Hit}      # define a hit collection
    CrystalSDData() = new(Hit[])    # default constructor
end

Sensitive Detector Functions#

#---Initialize method------------------------------------------------------------------------------
function crystal_initialize(::G4HCofThisEvent, data::CrystalSDData)::Nothing
    empty!(data.hitcollection)                                # empty the hit collection at every event
    return
end
#---Process Hit method-----------------------------------------------------------------------------
function crystal_processHits(step::G4Step, ::G4TouchableHistory, data::CrystalSDData)::Bool
    part = step |> GetTrack |> GetParticleDefinition
    edep = step |> GetTotalEnergyDeposit
    edep <  0. && return false
    pos = step |> GetPostStepPoint |> GetPosition
    push!(data.hitcollection, Hit(0., pos, edep, ScintCryst))  # fill the collection with a new Hit
    return true
end
crystal_processHits (generic function with 1 method)

SD Instance#

And finally construct the SD instance

#---Create SD instance-----------------------------------------------------------------------------
crystal_sd = G4JLSensitiveDetector("Crystal_SD", CrystalSDData();           # name an associated data are mandatory
             processhits_method=crystal_processHits, # process hits method (also mandatory)
             initialize_method=crystal_initialize)   # intialize method
Geant4.G4JLProtoSD{CrystalSDData}("Crystal_SD", CrystalSDData(Hit[]), Main.crystal_processHits, Main.crystal_initialize, nothing)

To test this created SD we will create a very simple detector geometry

struct SimpleDetector <: G4JLDetector ; end

#---Materials----------------------------------------------------------------------------------
nist = G4NistManager!Instance()
m_air = FindOrBuildMaterial(nist, "G4_AIR")
m_bgo = FindOrBuildMaterial(nist, "G4_BGO")

#---Volumes------------------------------------------------------------------------------------
worldS = G4Box("world", 1m, 1m, 1m)
worldLV = G4LogicalVolume(worldS, m_air, "World")
worldPV = G4PVPlacement(nothing, G4ThreeVector(), worldLV, "World", nothing, false, 0, false)

crystalS = G4Box("world", 5cm, 5cm, 20cm)
crystalLV = G4LogicalVolume(crystalS, m_bgo, "Crystal")
crystalPV = G4PVPlacement(nothing, G4ThreeVector(), crystalLV, "Crystal", worldLV, false, 0, false)

#---define getConstructor
Geant4.getConstructor(::SimpleDetector)::Function = (::SimpleDetector) -> worldPV

Define Application#

We will use the end-of-event user action to see how many hits we have produced. We could also at this moment to store the hits of some statistical data for each event. Be aware that the function is called from different threads. It is the responsibility of the developer to ensure that the function es thread safe. The function getSDdata ensures that you get a copy of the SD data that corresponds to the running thread.

#---End Event Action-------------------------------------------------------------------------------
function endeventaction(evt::G4Event, app::G4JLApplication)
    hits = getSDdata(app, "Crystal_SD").hitcollection
    eventID = evt |> GetEventID
    if eventID < 10 || eventID % 100 == 0
      G4JL_println("Event: $eventID with $(length(hits)) hits stored in this event")
    end
    return
end
endeventaction (generic function with 1 method)

We use a simple particle gun

particlegun = G4JLGunGenerator(particle = "e-", 
                               energy = 3GeV, 
                               direction = G4ThreeVector(0,0,1), 
                               position = G4ThreeVector(0,0,-1m))
G4JLGunGenerator("ParticleGun", Geant4.G4JLParticleGunData(nothing, "e-", G4ThreeVector(0.0,0.0,1.0), G4ThreeVector(0.0,0.0,-1000.0), 3000.0), Geant4.var"#init#19"(), Geant4.var"#gen#20"(), G4JLGeneratorAction[])
#---Create the Application-------------------------------------------------------------------------
app = G4JLApplication( detector = SimpleDetector(),                  # detector with parameters
                       generator = particlegun,                      # primary particle generator
                       nthreads = 4,                                 # number of threads (MT)
                       physics_type = FTFP_BERT,                     # what physics list to instantiate
                       endeventaction_method = endeventaction,       # end event action
                       sdetectors = ["Crystal" => crystal_sd]        # mapping of LVs to SDs (+ means multiple LVs with same name)
                      )
configure(app)
initialize(app)
beamOn(app,10)