Extended/TestEm3 Example

This example is based on the Geant4 extended/TestEm3 example of the Geant4 distribution. It can be used to test the electromagnetic physics processes in Geant4.

  • How to collect energy deposition in a sampling calorimeter.
  • How to survey energy flow.
  • How to print stopping power.

Geometry

The calorimeter is a box made of a given number of layers. A layer consists of a sequence of various absorbers (maximum MaxAbsor=9). The layer is replicated.

Parameters defining the calorimeter :

  • the number of layers,
  • the number of absorbers within a layer,
  • the material of the absorbers,
  • the thickness of the absorbers,
  • the transverse size of the calorimeter (the input face is a square).

In addition a transverse uniform magnetic field can be applied.

The default geometry is constructed in DetectorConstruction class, but all of the above parameters can be modified interactively via the commands defined in the DetectorMessenger class.

      |<----layer 0---------->|<----layer 1---------->|<----layer 2---------->|
      |           |           |                       |                       |
      ==========================================================================
      ||          |           ||          |           ||          |           ||
      ||          |           ||          |           ||          |           ||
      ||   abs 1  | abs 2     ||   abs 1  | abs 2     ||   abs 1  | abs 2     ||
      ||          |           ||          |           ||          |           ||
      ||          |           ||          |           ||          |           ||
beam  ||          |           ||          |           ||          |           ||
====> ||          |           ||          |           ||          |           ||
      ||          |           ||          |           ||          |           ||
      ||          |           ||          |           ||          |           ||
      ||          |           ||          |           ||          |           ||
      ||          |           ||          |           ||          |           ||
      ||   cell 1 | cell 2    ||   cell 3 | cell 4    ||   cell 5 | cell 6    ||
      ==========================================================================
      ^           ^           ^           ^           ^           ^           ^
      pln1        pln2        pln3       pln4        pln5        pln6       pln7
Note that

You can also download this example as a Jupyter notebook and a plain Julia source file.

Table of contents

Loading the necessary Julia modules

Load the Geant4 and Geant4.SystemOfUnits modules. We will also use the FHist and Plots modules to handle the histograms and plots, and Printf to format the output.

using Geant4
using Geant4.SystemOfUnits
using FHist, Plots, Printf
import DisplayAs: PNG

Define Detector structure

The detector is a sandwich calorimeter made of layers of sensitive and absorber materials. The detector is defined by the TestEm3Detector structure. We include the TestEm3Detector.jl file not to clutter the example. We can also use a GDML file to define the geometry. This is done by instantiating the G4JLDetectorGDML type with the GDML file as argument.

const useGdml = true
include(joinpath(@__DIR__, "TestEm3Detector.jl"))
if useGdml
    detector = G4JLDetectorGDML("$(@__DIR__)/TestEm3.gdml")
else
    detector = TestEm3Detector()
end
Geant4.G4JLDetectorGDML(CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4VPhysicalVolume}(Ptr{Geant4.G4VPhysicalVolume} @0x000000000171fd90))

Define Simulation Data struct

It consists on a number of counters and statistical accumulators to store the simulation results. In addition, it includes a number of histograms to store the results.

const Hist1D64 = Hist1D{Float64}
mutable struct TestEm3SimData <: G4JLSimulationData
    ##---Run data-----------------------------------------------------------------------------------
    fParticle::CxxPtr{G4ParticleDefinition}
    fEkin::Float64

    ##fSumEAbs::Vector{Float64}
    ##fSum2EAbs::Vector{Float64}
    ##fSumLAbs::Vector{Float64}
    ##fSum2LAbs::Vector{Float64}

    ##fEdepTot::Float64
    ##fEdepTot2::Float64
    ##fEleakTot::Float64
    ##fEleakTot2::Float64

    ##fEtotal::Float64
    ##fEtotal2::Float64

    ##fEnergyFlow::Vector{Float64}
    ##fLateralEleak::Vector{Float64}
    ##fEnergyDeposit::Matrix{Float64}

    fChargedStep::Int32
    fNeutralStep::Int32

    fN_gamma::Int32
    fN_elec::Int32
    fN_pos::Int32

    fEnergyDeposit::Vector{Float64}     ## Energy deposit per event
    fTrackLengthCh::Vector{Float64}     ## Track length per event

    fEdepEventHistos::Vector{Hist1D64}
    fTrackLengthChHistos::Vector{Hist1D64}
    fEdepHistos::Vector{Hist1D64}
    fAbsorLabel::Vector{String}

    ##G4double fEdeptrue [kMaxAbsor];
    ##G4double fRmstrue  [kMaxAbsor];
    ##G4double fLimittrue[kMaxAbsor];
    ##G4bool fApplyLimit;

    ##fTimer::Float64

    TestEm3SimData() = new()
end

We also define the add! function to reduce the simulation results between the master and worker threads.

function add!(x::TestEm3SimData, y::TestEm3SimData)
    x.fChargedStep += y.fChargedStep
    x.fNeutralStep += y.fNeutralStep
    x.fN_gamma += y.fN_gamma
    x.fN_elec += y.fN_elec
    x.fN_pos += y.fN_pos
    x.fEdepEventHistos += y.fEdepEventHistos
    x.fTrackLengthChHistos += y.fTrackLengthChHistos
    x.fEdepHistos += y.fEdepHistos
end
add! (generic function with 1 method)

Define a function to plot the simulation data from the application

function do_plot(data::TestEm3SimData)
    (;fEdepHistos, fEdepEventHistos, fTrackLengthChHistos, fAbsorLabel) = data
    lay = @layout [°; ° °]
    img = plot(layout=lay, show=true, size=(1400,1000))
    for (h, l) in zip(fEdepHistos, fAbsorLabel)
        plot!(subplot=1, h, title="Energy Deposition", xlabel="layer #", label=l, show=true)
    end
    for (h, l) in zip(fEdepEventHistos, fAbsorLabel)
        plot!(subplot=2, h, title="Energy/event Distribution", label=l, xlabel="MeV")
    end
    for (h, l) in zip(fTrackLengthChHistos, fAbsorLabel)
        plot!(subplot=3, h, title="Track Lengh Distribution", label=l, xlabel="mm")
    end
    return img
end
do_plot (generic function with 1 method)

Particle Gun initialization

particlegun = G4JLGunGenerator(particle = "e-",
                               energy = 1GeV,
                               direction = G4ThreeVector(1,0,0),
                               position = G4ThreeVector(0,0,0))  # temporary potition, will update once the detector is constructed
Geant4.G4JLGunGenerator("ParticleGun", Geant4.G4JLParticleGunData(nothing, "e-", G4ThreeVector(1.0,0.0,0.0), G4ThreeVector(0.0,0.0,0.0), 1000.0), Geant4.var"#init#19"(), Geant4.var"#gen#20"(), Geant4.G4JLGeneratorAction[])

Define the Simulation User Actions

##---Step action------------------------------------------------------------------------------------
function stepaction(step::G4Step, app::G4JLApplication)::Nothing
    detector = app.detector
    data = getSIMdata(app)
    prepoint = GetPreStepPoint(step)
    track = GetTrack(step)

    # Return if step in not in the world volume
    prepoint |> GetPhysicalVolume |> GetLogicalVolume |> GetMaterial == detector.fWorldMaterial && return nothing

    particle = GetDefinition(track)
    charge  = GetPDGCharge(particle)
    stepl = 0.
    if charge != 0.
        stepl = GetStepLength(step)
        data.fChargedStep += 1
    else
        data.fNeutralStep += 1
    end
    edep = GetTotalEnergyDeposit(step) * GetWeight(track)
    absorNum  = GetCopyNumber(GetTouchable(prepoint), 0)
    layerNum  = GetCopyNumber(GetTouchable(prepoint), 1) + 1  ## replicas copynumber starts at 0

    data.fEnergyDeposit[absorNum] += edep
    data.fTrackLengthCh[absorNum] += stepl

    push!(data.fEdepHistos[absorNum], layerNum, edep)
    nothing
end

##---Tracking pre-action----------------------------------------------------------------------------
let G4Gamma, G4Electron, G4Positron, first=true
global function pretrackaction(track::G4Track, app::G4JLApplication)::Nothing
    if first
        G4Gamma    = FindParticle("gamma")
        G4Electron = FindParticle("e-")
        G4Positron = FindParticle("e+")
        first = false
    end
    data = getSIMdata(app)
    d = GetDefinition(track)
    if d === G4Gamma
        data.fN_gamma += 1
    elseif d === G4Electron
        data.fN_elec +=1
    elseif d === G4Positron
        data.fN_pos += 1
    end
    nothing
end
end

##---Tracking post-action---------------------------------------------------------------------------
function posttrackaction(track::G4Track, ::G4JLApplication)::Nothing
  nothing
end

##---Begin Run Action-------------------------------------------------------------------------------
function beginrun(run::G4Run, app::G4JLApplication)::Nothing
    data = getSIMdata(app)
    (; fNbOfAbsor, fNbOfLayers, fAbsorMaterial, fAbsorThickness) = app.detector
    gun = app.generator.data.gun
    data.fParticle = GetParticleDefinition(gun)
    data.fEkin = GetParticleEnergy(gun)
    data.fN_gamma = data.fN_elec = data.fN_pos = 0
    data.fChargedStep = data.fNeutralStep = 0
    # init arrays
    data.fEnergyDeposit = zeros(fNbOfAbsor)
    data.fTrackLengthCh = zeros(fNbOfAbsor)
    data.fEdepHistos = [Hist1D(;counttype=Float64,binedges=0.:1.0:fNbOfLayers) for i in 1:fNbOfAbsor]
    data.fEdepEventHistos = [Hist1D(;binedges=0.:10.:1000.) for i in 1:fNbOfAbsor]
    data.fTrackLengthChHistos = [Hist1D(;binedges=0.:20.:2000.) for i in 1:fNbOfAbsor]
    data.fAbsorLabel = ["$(fAbsorThickness[i])mm of $(fAbsorMaterial[i] |> GetName |> String)" for i in 1:fNbOfAbsor]
    nothing
end
##---End Run Action---------------------------------------------------------------------------------
function endrun(run::G4Run, app::G4JLApplication)::Nothing
    ##---end run action is called for each workwer thread and the master one
    if G4Threading!G4GetThreadId() < 0
        data = app.simdata[1]
        ##---This is the master thread, so we need to add all the simuation results-----------------
        for d in app.simdata[2:end]
            add!(data, d)
        end
        nEvt = GetNumberOfEvent(run)
        norm = nEvt > 0 ? 1/nEvt : 1.

        @printf "------------------------------------------------------------\n"
        @printf " Beam particle %s E = %.2f GeV\n" String(GetParticleName(data.fParticle)) data.fEkin/GeV
        @printf " Mean number of gamma          %.2f\n" data.fN_gamma*norm
        @printf " Mean number of e-             %.2f\n" data.fN_elec*norm
        @printf " Mean number of e+             %.2f\n" data.fN_pos*norm
        @printf " Mean number of charged steps  %f\n"   data.fChargedStep*norm
        @printf " Mean number of neutral steps  %f\n"   data.fNeutralStep*norm
        @printf "------------------------------------------------------------"
    else
        G4JL_println("end-run  for worker $(G4Threading!G4GetThreadId())")
    end
end

##---Begin Event Action-----------------------------------------------------------------------------
function beginevent(evt::G4Event, app::G4JLApplication)
    data = getSIMdata(app)
    fill!(data.fEnergyDeposit, 0.0)
    fill!(data.fTrackLengthCh, 0.0)
    nothing
end

##---End Event Action-------------------------------------------------------------------------------
function endevent(evt::G4Event, app::G4JLApplication)
    data = getSIMdata(app)
    (; fNbOfAbsor, fNbOfLayers) = app.detector
    for i in 1:fNbOfAbsor
        push!(data.fEdepEventHistos[i], data.fEnergyDeposit[i])
        push!(data.fTrackLengthChHistos[i], data.fTrackLengthCh[i])
    end
    nothing
end
endevent (generic function with 1 method)

Create the Application

app = G4JLApplication(detector = TestEm3Detector(),               ## detector with parameters
                      simdata = TestEm3SimData(),                 ## simulation data structure
                      generator = particlegun,                    ## primary particle generator
                      nthreads = VERSION > v"1.9" ? 4 : 0,        ## number of threads (MT)
                      physics_type = FTFP_BERT,                   ## what physics list to instantiate
                      ##----Actions--------------------------------
                      stepaction_method = stepaction,             ## step action method
                      pretrackaction_method = pretrackaction,     ## pre-tracking action
                      posttrackaction_method = posttrackaction,   ## post-tracking action
                      beginrunaction_method = beginrun,           ## begin-run action (initialize counters and histograms)
                      endrunaction_method = endrun,               ## end-run action (print summary)
                      begineventaction_method = beginevent,       ## begin-event action (initialize per-event data)
                      endeventaction_method = endevent            ## end-event action (fill histogram per event data)
                      )
Geant4.G4JLApplication{Main.var"##230".TestEm3Detector, Main.var"##230".TestEm3SimData}(Geant4.G4MTRunManagerAllocated(Ptr{Nothing} @0x0000000000d80ad0), Main.var"##230".TestEm3Detector(2, 50, 400.0, [2.3, 5.7], 8.0, 400.0, 480.0, 480.0, CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4Material}[CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4Material}(Ptr{Geant4.G4Material} @0x00000000017f5bd0), CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4Material}(Ptr{Geant4.G4Material} @0x0000000000f28f10)], CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4Material}(Ptr{Geant4.G4Material} @0x00000000014f5090), #undef, #undef, CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4VPhysicalVolume}(Ptr{Geant4.G4VPhysicalVolume} @0x000000000000000b), #undef, #undef, CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4VPhysicalVolume}(Ptr{Geant4.G4VPhysicalVolume} @0xffffffffffffffff), #undef, #undef, CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4VPhysicalVolume}(Ptr{Geant4.G4VPhysicalVolume} @0x0000000000000011), #undef, #undef, #undef), Main.var"##230".TestEm3SimData[Main.var"##230".TestEm3SimData(CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4ParticleDefinition}(Ptr{Geant4.G4ParticleDefinition} @0x0000000000000009), 6.90246874733157e-310, 1, 0, 2, 0, 3, #undef, #undef, #undef, #undef, #undef, #undef), Main.var"##230".TestEm3SimData(CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4ParticleDefinition}(Ptr{Geant4.G4ParticleDefinition} @0x0000000000000009), 6.90246874733157e-310, 1, 0, 2, 0, 3, #undef, #undef, #undef, #undef, #undef, #undef), Main.var"##230".TestEm3SimData(CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4ParticleDefinition}(Ptr{Geant4.G4ParticleDefinition} @0x0000000000000009), 6.90246874733157e-310, 1, 0, 2, 0, 3, #undef, #undef, #undef, #undef, #undef, #undef), Main.var"##230".TestEm3SimData(CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4ParticleDefinition}(Ptr{Geant4.G4ParticleDefinition} @0x0000000000000009), 6.90246874733157e-310, 1, 0, 2, 0, 3, #undef, #undef, #undef, #undef, #undef, #undef), Main.var"##230".TestEm3SimData(CxxWrap.CxxWrapCore.CxxPtr{Geant4.G4ParticleDefinition}(Ptr{Geant4.G4ParticleDefinition} @0x0000000000000009), 6.90246874733157e-310, 1, 0, 2, 0, 3, #undef, #undef, #undef, #undef, #undef, #undef)], Geant4.G4JLGunGenerator("ParticleGun", Geant4.G4JLParticleGunData(nothing, "e-", G4ThreeVector(1.0,0.0,0.0), G4ThreeVector(0.0,0.0,0.0), 1000.0), Geant4.var"#init#19"(), Geant4.var"#gen#20"(), Geant4.G4JLGeneratorAction[]), nothing, nothing, 4, 0, Geant4.G4MTRunManager, Geant4.G4JLDetectorConstruction, Geant4.FTFP_BERT, Geant4.G4JLRunAction, Geant4.G4JLEventAction, Geant4.G4JLTrackingAction, Geant4.G4JLSteppingAction, Main.var"##230".stepaction, Main.var"##230".pretrackaction, Main.var"##230".posttrackaction, Main.var"##230".beginrun, Main.var"##230".endrun, Main.var"##230".beginevent, Main.var"##230".endevent, nothing, nothing, Dict{String, Geant4.G4JLProtoSD}(), Dict{String, Vector{Geant4.G4JLSensitiveDetector}}(), Geant4.G4JLScoringMesh[], nothing, nothing)

Configure, Initialize and Run

configure(app)
initialize(app)
SetParticlePosition(particlegun, G4ThreeVector(-app.detector.fWorldSizeX/2,0,0))  ## Only now is known the size of the 'world'

beamOn(app, 1000)
Building Geometry now!!!
### G4LevelReader: broken transition 0 from level 24 to 24 for isotope Z= 89 A= 219 - use ground level
G4WT2 > end-run  for worker 2
G4WT3 > end-run  for worker 3
G4WT0 > end-run  for worker 0
G4WT1 > end-run  for worker 1
------------------------------------------------------------
 Beam particle e- E = 1.00 GeV
 Mean number of gamma          521.20
 Mean number of e-             896.44
 Mean number of e+             53.27
 Mean number of charged steps  3741.658000
 Mean number of neutral steps  3716.913000
------------------------------------------------------------

Plot the results

img = do_plot(app.simdata[1])
PNG(img)

Change the primary particle enerqy and run the simulation again

SetParticleEnergy(particlegun, 100MeV)
beamOn(app, 1000)
img = do_plot(app.simdata[1])
PNG(img)

Change the geometry and run the simulation again

Add a new absorber layer

reinitialize(app, TestEm3Detector(absorThickness = [2.3mm, 5.7mm, 1mm],
                                  absorMaterial = ["G4_Pb", "G4_lAr", "G4_Al"]))
beamOn(app, 1000)
img = do_plot(app.simdata[1])
PNG(img)


This page was generated using Literate.jl.