Testing G4GeneralParticleSource

See the G4GeneralParticleSource documentation for the definition of parameters.

using Parameters
using Geant4
using Geant4.PhysicalConstants
using Geant4.SystemOfUnits
using Geant4.SystemOfUnits: deg
using FHist
using Plots
#---Choose the Makie backend
#using GLMakie
#using WGLMakie
using CairoMakie
using GeometryBasics, Rotations, IGLWrap_jll  # to force loding G4Vis extension

Detector definition and drawing

# Define the detector
include(joinpath(@__DIR__, "DetectorGPS.jl"))

# Draw the detector
det = GPSDetector()
world = construct(det)
img = draw(world[])
display("image/png", img)

png

Simulation data

The simulation data GPSSimData struct is a set of histograms that will be filled in the user tracking action and can be visualised with the function do_plot(). It is using the macro @with_kw from the Parameters.jl package to construct an instance with all the defaults.

const nbins = 50
@with_kw mutable struct GPSSimData <: G4JLSimulationData
    hKEne     = H1D("Energy Spectrum", nbins, 0., 15., :MeV)
    hRad      = H1D("Radial ditribution", nbins, 0., 10., :cm)
    hAngcosθ  = H1D("Angular ditribution cosθ", nbins, -1., 1.)
    hAngϕ     = H1D("Angular distribution ϕ", nbins, 0.,360., :deg)
    hXYpos    = H2D("Source X-Y distribution", nbins, -10., 10., nbins, -10., 10., (:cm, :cm))
    hZXpos    = H2D("Source Z-X distribution", nbins, -10., 10., nbins, -10., 10., (:cm, :cm))
    hYZpos    = H2D("Source Y-Z distribution", nbins, -10., 10., nbins, -10., 10., (:cm, :cm))
    hcosθϕ    = H2D("Angular cos(θ)-ϕ distribution", nbins, -1., 1., nbins, 0., 360., (:nounit, :deg))
    hθϕ       = H2D("Angular θ-ϕ distribution", nbins, 0., 180., nbins, 0., 360., (:deg, :deg))
end
#---merge and empty functions----------------------------------------------------------------------
function Base.merge!(x::D, y::D) where D <: G4JLSimulationData
    for f in fieldnames(D)
        merge!(getfield(x,f), getfield(y,f))
    end
end
function Base.empty!(x::D) where D <: G4JLSimulationData
    for f in fieldnames(D)
        empty!(getfield(x,f))
    end
end
#---plot function----------------------------------------------------------------------------------
function do_plot(data::GPSSimData)
    img = Plots.plot(layout=(3,3), show=true, size=(1400,1000))
    for (i,fn) in enumerate(fieldnames(GPSSimData))
        h = getfield(data, fn)
        Plots.plot!(subplot=i, h.hist, title=h.title, show=true, cgrad=:plasma)
    end
    return img
end
do_plot (generic function with 1 method)

User Actions

The begin and end run action is used to initialise and sum (merge) the simulation data. The tracking action is used to fill the histograms.

#---Run Actions------------------------------------------------------------------------------------
function beginrun(::G4Run, app::G4JLApplication)::Nothing
    data = getSIMdata(app)
    empty!(data) 
    nothing
end
function endrun(::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]
            merge!(data, d)
        end
    end
end
#---Tracking Actions-----------------------------------------------------------------------------
function pretrackaction(track::G4Track, app::G4JLApplication)::Nothing
    data = getSIMdata(app)
    ekin = track |> GetKineticEnergy
    vertex = track |> GetPosition
    direction = track |> GetMomentumDirection
    weight = track |> GetWeight

    x = vertex |> getX
    y = vertex |> getY
    z = vertex |> getZ
    θ = direction |> getTheta
    ϕ = direction |> getPhi
    ϕ < 0 &&  (ϕ += 2π)
    r = vertex |> mag
    dr = binedges(data.hRad.hist).uniform_edges.step |> Float64
    dv = 4π * r^2 * dr

    # fill histograms
    push!(data.hKEne, ekin)
    push!(data.hRad, r, 1.0/dv)
    push!(data.hAngcosθ, cos(θ))
    push!(data.hAngϕ, ϕ)
    push!(data.hXYpos, x, y)
    push!(data.hZXpos, z, x)
    push!(data.hYZpos, y, z)
    push!(data.hcosθϕ, cos(θ), ϕ)
    push!(data.hθϕ, θ, ϕ)
    nothing
end
pretrackaction (generic function with 1 method)

Particle Generator

We create a G4JLGeneralParticleSource particle generator. The initial run will be equivalent to the G4JLParticleGun with the same parameters.

#---Particle Gun initialization--------------------------------------------------------------------
gps = G4JLGeneralParticleSource(particle = "geantino", 
                                energy = 10MeV, 
                                direction = G4ThreeVector(1,0,0), 
                                position = G4ThreeVector(1,2,1))
G4JLGeneralParticleSource("GPS", Geant4.G4JLGPSData(nothing, @NamedTuple{particle::String, energy::Float64, direction::Geant4.CLHEP!Hep3VectorAllocated, position::Geant4.CLHEP!Hep3VectorAllocated}[(particle = "geantino", energy = 10.0, direction = G4ThreeVector(1.0,0.0,0.0), position = G4ThreeVector(1.0,2.0,1.0))], false, false), Geant4.initGPS, Geant4.var"#gen#22"(), G4JLGeneratorAction[])

Application definition, configuration and initialization

#---Create the Application-------------------------------------------------------------------------
app = G4JLApplication(detector = GPSDetector(),                   # detector with parameters
                      simdata = GPSSimData(),                     # simulation data structure
                      generator = gps,                            # primary particle generator 
                      nthreads = VERSION > v"1.9" ? 4 : 0,        # number of threads (MT)
                      physics_type = FTFP_BERT,                   # what physics list to instantiate
                      #----Actions--------------------------------
                      pretrackaction_method = pretrackaction,     # pre-tracking action
                      beginrunaction_method = beginrun,           # begin-run action (initialize counters and histograms)
                      endrunaction_method = endrun,               # end-run action (print summary)               
                      );
configure(app)
initialize(app)
**************************************************************
 Geant4 version Name: geant4-11-02-patch-01 [MT]   (16-February-2024)
  << in Multi-threaded mode >> 
                       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/
**************************************************************

G4Material WARNING: duplicate name of material Vacuum
G4Material WARNING: duplicate name of material Aluminium
G4Material WARNING: duplicate name of material Silicon oxide

Tests

Taken from examples\extended\eventgenerator\exgps\macros

Initial Particle Gun

beamOn(app, 1000)
img = do_plot(app.simdata[1])
display("image/png", img)

png

Test01

/gps/particle proton
/gps/pos/type Point
/gps/pos/centre 1. 2. 1. cm
/gps/ang/type iso
/gps/energy 2. MeV
reinitialize(app.generator;  particle="geantino",
                             energy=2MeV,
                             pos=(type="Point", centre=G4ThreeVector(1cm,2cm,1cm)),
                             ang=(type="iso"))
beamOn(app, 1000)
img = do_plot(app.simdata[1])
display("image/png", img)

png

Test02

# square plane source
/gps/pos/type Plane
/gps/pos/shape Square
/gps/pos/centre 1. 2. 1. cm
/gps/pos/halfx 2. cm
/gps/pos/halfy 2. cm

#cosine-law distribution
/gps/ang/type cos

# linear energy distr.
/gps/ene/type Lin
/gps/ene/min 2. MeV
/gps/ene/max 10. MeV
/gps/ene/gradient 1.
/gps/ene/intercept 1.
reinitialize(app.generator;  particle="geantino",
                             ene=(type="Lin", min=2MeV, max=10MeV, gradient=1, intercept=1),
                             pos=(type="Plane", shape="Square", centre=G4ThreeVector(1cm,2cm,1cm), halfx=2cm, halfy=2cm),
                             ang=(type="cos",mintheta=10deg, maxtheta=80deg))
beamOn(app, 10000)
img = do_plot(app.simdata[1])
display("image/png", img)

png

Test03

#rectangle plane source
/gps/pos/type Plane
/gps/pos/shape Rectangle
/gps/pos/centre 0. 2. 1. cm
/gps/pos/halfx 1. cm
/gps/pos/halfy 3. cm

# isotropic angular distri.
/gps/ang/type iso

# power-law energy distr.
/gps/ene/type Pow
/gps/ene/min 2. MeV
/gps/ene/max 10. MeV
/gps/ene/alpha -2.
reinitialize(app.generator;  particle="geantino",
                             ene=(type="Pow", min=2MeV, max=10MeV, alpha=-2),
                             pos=(type="Plane", shape="Rectangle", centre=G4ThreeVector(1cm,2cm,1cm), halfx=1cm, halfy=3cm),
                             ang=(type="iso", mintheta=0deg, maxtheta=180deg))
beamOn(app, 10000)
img = do_plot(app.simdata[1])
display("image/png", img)

png

Test04 and Test05 Together

  • source 1
    #circular plane source
    /gps/pos/type Plane
    /gps/pos/shape Circle
    /gps/pos/centre 2. 1. 0. cm
    /gps/pos/radius 3. cm

    # cosine-law 
    /gps/ang/type cos

    # exponential energy distri.
    /gps/ene/type Exp
    /gps/ene/min 2. MeV
    /gps/ene/max 10. MeV
    /gps/ene/ezero 2.
  • source 2
    # ellipse plane source
    /gps/pos/type Plane
    /gps/pos/shape Ellipse
    /gps/pos/centre 3. 1. 0. cm
    /gps/pos/halfx 1. cm
    /gps/pos/halfy 2. cm

    #isotropic angular distribution
    /gps/ang/type iso

    # brems energy distr.
    /gps/ene/type Brem
    /gps/ene/min 2. MeV
    /gps/ene/max 10. MeV
    /gps/ene/temp 2e12
reinitialize(app.generator;  sources = [(particle="geantino", intensity=0.5,
                                           ene=(type="Exp", min=2MeV, max=10MeV, ezero=2.),
                                           pos=(type="Plane", shape="Circle", centre=G4ThreeVector(1cm,2cm,0cm), radius=3cm),
                                           ang=(type="cos", mintheta=0deg, maxtheta=180deg)),
                                         (particle="geantino", intensity=0.5,
                                           ene=(type="Brem", min=2MeV, max=10MeV, temp=2e12),
                                           pos=(type="Plane", shape="Ellipse", centre=G4ThreeVector(3cm,1cm,0cm), halfx=1cm, halfy=2cm),
                                           ang=(type="iso", mintheta=0deg, maxtheta=180deg))])
beamOn(app, 100000)
img = do_plot(app.simdata[1])
display("image/png", img)

png