Defining Primary Particles

Defining Primary Particles#

A Geant4 application requires a source of primary particles. We have several possibilities to provide a primary particle generator.

  • Particle Gun - This is the simplest primary particle generator. It is a single type of particle with a fix energy and direction

  • General Particle Source - This is a generalization of a particle gun with many more parameters to configure its behavior. See the G4GeneralParticleSource documentation.

  • Full customization - The user can write code to decide the primary particles to create, as well as, their energies and directions for each event.

We are going to exercise the three cases in this notebook

using Geant4
using Geant4.SystemOfUnits
# Do some analysis of energy, positions and directions
using DataFrames, Plots 

function generate(generator, N)
    df = DataFrame(energy = Float32[], 
               pos_x = Float32[], pos_y = Float32[], pos_z = Float32[],
               dir_x = Float32[], dir_y = Float32[], dir_z = Float32[])
    for i in 1:N
        evt = G4Event()
        generator.gen_method(evt, generator.data)
        pos = evt |> GetPrimaryVertex |> GetPosition
        dir = evt |> GetPrimaryVertex |> GetPrimary |> GetMomentumDirection
        ene = evt |> GetPrimaryVertex |> GetPrimary |> GetKineticEnergy
        push!(df, (ene, x(pos), y(pos), z(pos), x(dir), y(dir), z(dir)))
    end
    return df
end
generate (generic function with 1 method)

Particle Gun#

We have introduced a new type G4JLGunGenerator to facilitate the task of defining a

particlegun = G4JLGunGenerator(particle = "pi+", 
                               energy = 330MeV, 
                               direction = G4ThreeVector(0, 0, 1), 
                               position = G4ThreeVector(0, 0, 0));

The actual G4ParticleGun object (the data.gun field) has not been created yet. It will be done by the G4RunManager during initialization. If we do initialize by hand now we will get a error since particles have not been created yet.

Lets build an application with just the generator

app = G4JLApplication(
    generator = particlegun,
)
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/
**************************************************************

Run one event and see the printout of the initial particle

ui`/tracking/verbose 1`
beamOn(app,1)
*********************************************************************************************************
* G4Track Information:   Particle = pi+,   Track ID = 1,   Parent ID = 0
*********************************************************************************************************

Step#    X(mm)    Y(mm)    Z(mm) KinE(MeV)  dE(MeV) StepLeng TrackLeng  NextVolume ProcName
    0        0        0        0       330        0        0         0       World initStep
    1        0        0    1e+03       330 2.73e-23    1e+03     1e+03  OutOfWorld Transportation

We can easily change the parameters of the particle gun with the following methods

SetParticleByName(particlegun, "e-")
SetParticleEnergy(particlegun, 10GeV)
SetParticleMomentumDirection(particlegun, G4ThreeVector(1,0,0))

beamOn(app,1)
*********************************************************************************************************
* G4Track Information:   Particle = e-,   Track ID = 1,   Parent ID = 0
*********************************************************************************************************

Step#    X(mm)    Y(mm)    Z(mm) KinE(MeV)  dE(MeV) StepLeng TrackLeng  NextVolume ProcName
    0        0        0        0     1e+04        0        0         0       World initStep
    1    1e+03        0        0     1e+04 5.12e-23    1e+03     1e+03  OutOfWorld Transportation

General Particle Source#

You can create a G4JLGeneralParticleSource with the same parameters (and same behavior) as the Particle Gun.

gps = G4JLGeneralParticleSource(particle = "proton", 
                                energy = 10MeV, 
                                direction = G4ThreeVector(1,0,0), 
                                position = G4ThreeVector(0,0,0));
app = G4JLApplication(
    generator = gps,
)
configure(app)
initialize(app)
df = generate(gps, 1000)
histogram(df.energy)

Note that The exception is due to the fact that Geant4 does not like to re-initialize. Please ignore the exception and look at the created particle. Now we can change the parameters in two different ways:

  • using the UI commands

  • instantiating a new G4JLGeneralParticleSource with the needed parameters

reinitialize(app.generator;  particle="geantino",
                             energy=2MeV,
                             pos=(type="Point", centre=G4ThreeVector(1cm,2cm,1cm)),
                             ang=(type="iso"))
df = generate(gps, 1000)
histogram(df.energy)
reinitialize(gps;  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))
df = generate(gps, 1000)
histogram(df.energy)
scatter(df.pos_x, df.pos_y, df.pos_z)

Custom Primary Generator#

The user can provide fully custom primary particle generator. This is done by defining custom structure to configure the generator and two functions to initialize and generate the primary particles that will be called by the G4RunManager for each event.

The user configuration data structure should inherit from G4JLGeneratorData abstract type. Here is an example of a generator of a for a rectangle shape origined a mono-energy particles.

# define the data structure with the generator parameters
mutable struct PlaneSourceData <: G4JLGeneratorData
    particleName::String                         # particle type
    particlePtr::CxxPtr{G4ParticleDefinition}    # keep the pointer to definition for performance
    energy::Float64                              # kinetic energy
    halfx::Float64                               # rectangle dimensions
    halfy::Float64
    position::G4ThreeVector                      # rectangle origin
    direction::G4ThreeVector                     # particle direction
end

We defile now a function PlaneSource that will create an instance of G4JLPrimaryGenerator with the configuration data (using some default values) and the two functions: init and generate. The init converts the particle name to a definition, which is then used for the generate function at each event. The generate generate random points en X and Y and creates a G4PrimaryParticle and G4PrimaryVertex which are added to the event given as argument.

# define the constructor with the default parameters
function PlaneSource(;particle="gamma", energy=0.07MeV, halfx=7cm, halfy=7cm, 
                                        position=G4ThreeVector(0,0,0), 
                                        direction=G4ThreeVector(0,0,1))
    data = PlaneSourceData(particle, CxxPtr{G4ParticleDefinition}(C_NULL), energy, halfx, halfy, position, direction)

    function init(data:: PlaneSourceData, app::G4JLApplication)
        data.particlePtr = FindParticle(data.particleName)
    end

    function generate( evt::G4Event, data:: PlaneSourceData)::Nothing
        mass = data.particlePtr |> GetPDGMass
        momentum = ((mass + data.energy)^2 - mass^2)
        pvec = momentum * data.direction
        pos = data.position + G4ThreeVector( data.halfx * (rand() - 0.5),  data.halfy * (rand() - 0.5), 0)
        primary = G4PrimaryParticle(data.particlePtr, pvec |> x, pvec |> y, pvec |> z )
        vertex = G4PrimaryVertex(pos, 0ns)
        SetPrimary(vertex, move!(primary))    # note that we give up ownership of the objects just created
        AddPrimaryVertex(evt, move!(vertex))  # note that we give up ownership of the objects just created
    end
    G4JLPrimaryGenerator("PlaneSource", data; init_method=init, generate_method=generate)
end
PlaneSource (generic function with 1 method)
planesource = PlaneSource(energy=10MeV, halfx=10cm, halfy=10cm)
G4JLPrimaryGenerator{PlaneSourceData}("PlaneSource", PlaneSourceData("gamma", CxxPtr{G4ParticleDefinition}(Ptr{G4ParticleDefinition} @0x0000000000000000), 10.0, 100.0, 100.0, G4ThreeVector(0.0,0.0,0.0), G4ThreeVector(0.0,0.0,1.0)), var"#init#2"(), var"#generate#3"(), G4JLGeneratorAction[])
app = G4JLApplication(
    generator = planesource,
)
configure(app)
initialize(app)
df = generate(planesource, 1000)
histogram(df.energy)
scatter(df.pos_x, df.pos_y)
scatter(df.pos_x, df.pos_y, df.pos_z)