Building Applications

Building Applications#

What is still missing to build complete applications is to add user actions or sensitive detectors or scorers. In this tutorial we have a look at the user actions.

User Actions#

User actions are native Julia functions that will be called by the Geant4 toolkit at the defined moment during the simulation. They

The following are the possible user actions:

  • stepping action. Called on each simulation step. The signature is (::G4Step, ::G4JLApplication)::Nothing. Consult the G4Step reference manual to see what you can get from it.

  • pre-tracking action. Called at the creation of a new participle being tracked. The signature is (::G4Track, ::G4JLApplication)::Nothing. Consult the G4Step reference manual to see what you can get from it.

  • post-tracking action. Called at the end of the particle being tracked. The signature is (::G4Track, ::G4JLApplication)::Nothing. Consult the G4Track reference manual to see what you can get from it.

  • begin-event action. Called at the beginning of each event. The signature is (::G4Event, ::G4JLApplication)::Nothing. Consult the G4Event reference manual to see what you can get from it.

  • end-event action. Called at the end of each event. The signature is (::G4Event, ::G4JLApplication)::Nothing. Consult the G4Event reference manual to see what you can get from it.

  • begin-run action. Called at the beginning of a run. The signature is (::G4Run, ::G4JLApplication)::Nothing. Consult the G4Run reference manual to see what you can get from it.

  • end-run action. Called at the end of a run. The signature is (::G4Run, ::G4JLApplication)::Nothing. Consult the G4Run reference manual to see what you can get from it.

  • stack action. Called when a particle is going to be put back on the particle stack. The signature is (::G4Track, ::G4JLApplication)::G4ClassificationOfNewTrack. Consult the G4Track reference manual to see what you can get from it.

All user actions receive in addition to the standard G4 arguments a reference to the G4JLApplication object from which the user can get additional information on the application. These are the available field:

  • detector - user defined detector parameters object

  • simdata - vector of the user defined simulation data objects (one per worker thread plus the accumulated one). The function getSIMdata(app) return the simulation data corresponding to the threadid of the worker calling the user action.

  • generator - primary particle generator

  • field - magnetic field

  • nthreads - number of worker threads

  • verbose - verbosity level

Ploase note that: user actions need to be coded as thread-safe (global data should not be modified without any protection). However, this notebook not use multi-threading in order to be able to re-configure the G4JLApplication with different actions.

using Geant4

Let’s begin with something very simple. To define a begin event action that prints a message (notice that we use G4JL_println instead of the native println to ensure thread safety.)

function beginevent(evt::G4Event, app::G4JLApplication)
    G4JL_println("started event $(evt |> GetEventID)")
end
beginevent (generic function with 1 method)
app = G4JLApplication(begineventaction_method=beginevent)
configure(app)
initialize(app)
**************************************************************
 Geant4 version Name: geant4-11-02-patch-01 [MT]   (16-February-2024)
                       Copyright : Geant4 Collaboration
                      References : NIM A 506 (2003), 250-303
                                 : IEEE-TNS 53 (2006), 270-278
                                 : NIM A 835 (2016), 186-225
                             WWW : http://geant4.org/
**************************************************************
beamOn(app,5)
started event 0
started event 1
started event 2
started event 3
started event 4

Please notice that the output message would be prefixed by the working thread ID and the order of execution of the events is not sequential in case we enable multi-threading.

function endevent(evt::G4Event, app::G4JLApplication)
    G4JL_println("end event $(evt |> GetEventID)")
end
endevent (generic function with 1 method)
app = G4JLApplication(begineventaction_method = beginevent,
                      endeventaction_method = endevent)
configure(app)
initialize(app)
beamOn(app,5)
started event 0
end event 0
started event 1
end event 1
started event 2
end event 2
started event 3
end event 3
started event 4
end event 4
function stepaction(step::G4Step, app::G4JLApplication)
    G4JL_println("step with length $(step |> GetStepLength)")
end
stepaction (generic function with 1 method)
app = G4JLApplication(begineventaction_method = beginevent,
                      endeventaction_method = endevent,
                      stepaction_method = stepaction)
configure(app)
initialize(app)
beamOn(app,5)
started event 0
step with length 1000.0
end event 0
started event 1
step with length 1000.0
end event 1
started event 2
step with length 1000.0
end event 2
started event 3
step with length 1000.0
end event 3
started event 4
step with length 1000.0
end event 4

Defining Simulation Data#

The user can define a custom data structure to collect simulation data during the execution of the user actions. Here is an example:

using DataFrames
mutable struct MySimData <: G4JLSimulationData
    steps::DataFrame
    MySimData() = new(DataFrame( X = Float64[], Y = Float64[], Z = Float64[],
                                 KinE = Float64[], dE =  Float64[], StepLeng = Float64[]))
end
function stepaction(step::G4Step, app::G4JLApplication)
    steps = getSIMdata(app).steps                     # Get it own copy of the simdata (avoid data racing in MT mode)
    point = step |>  GetPreStepPoint |> GetPosition
    push!(steps, (point |> x, point |> y, point |> z,
                  step |> GetTrack |> GetKineticEnergy, step |> GetTotalEnergyDeposit, step |> GetStepLength ))
    return
end

app = G4JLApplication(simdata = MySimData(),
                      begineventaction_method = beginevent,
                      endeventaction_method = endevent,
                      stepaction_method = stepaction)
configure(app)
initialize(app)
beamOn(app,5)
started event 0
end event 0
started event 1
end event 1
started event 2
end event 2
started event 3
end event 3
started event 4
end event 4

To get the results we can access app.simdata[1]

app.simdata[1]
MySimData(5×6 DataFrame
 Row │ X        Y        Z        KinE     dE           StepLeng
     │ Float64  Float64  Float64  Float64  Float64      Float64
─────┼───────────────────────────────────────────────────────────
   1 │     0.0      0.0      0.0     10.0  3.03438e-23    1000.0
   2 │     0.0      0.0      0.0     10.0  3.03438e-23    1000.0
   3 │     0.0      0.0      0.0     10.0  3.03438e-23    1000.0
   4 │     0.0      0.0      0.0     10.0  3.03438e-23    1000.0
   5 │     0.0      0.0      0.0     10.0  3.03438e-23    1000.0)