KAHIBARO
Discord Login Register

31.1. Organizing Source Code

Why Source Code Organization Matters

For small test programs, you can put almost everything into a few files and still manage to find your way around. For any realistic Geant4 application, this quickly becomes painful. Clear source code organization makes it easier to understand your simulation, to extend it, to debug it, and to let others (or your future self) work on it.

Geant4 encourages a modular structure through its class design. Your goal is to reflect that modularity in your directory layout, file naming, and dependencies between parts of the code. In this chapter we focus on practical structure, not on C++ syntax or Geant4 physics.

A well organized project:

  1. Separates independent concerns into separate files and directories.
  2. Keeps dependencies one way, from high level to low level, not in circles.
  3. Groups related classes so you can find and reuse them.

Mirror the Geant4 Simulation Structure

A simple way to start is to mirror the main conceptual blocks of a Geant4 simulation. You already know the roles of geometry, physics, primary generation, and actions from earlier chapters. Each of these can correspond to groups of source files.

Typically, you will have one main file that creates the run manager and plugs in user classes. Around this center you organize user code for geometry, physics, primary generation, and analysis. This keeps each part focused, with clear responsibilities, and avoids mixing unrelated logic inside single classes.

When the project grows, you can subdivide these groups further. For example, geometry can be split into world geometry, individual detector components, and support structures. Actions can separate event level, track level, and step level logic into their respective classes, not only because Geant4 requires it, but also because it keeps different levels of logic clearly isolated.

Typical Directory Layout for a Geant4 Project

Although Geant4 does not enforce a particular directory layout, a conventional structure makes your project easier to understand and use. A common pattern is to split the project into source code, headers, build directory, macros, and optional configuration or data files.

A simple and practical layout looks like this:

Directory or fileTypical contents
CMakeLists.txtBuild configuration for the whole project
src/All .cc implementation files
include/All .hh header files
macros/Macro files used to configure and run simulations
data/Additional data files specific to the application
vis/Optional visualization macros or styles
build/Out of source build directory, created by you

Inside src and include you can further group related classes into subdirectories. For example, geometry classes under geometry, detector specific code under detectors, and analysis classes under analysis. The important point is consistency. If a class header is in include/geometry, its implementation belongs under src/geometry with the same base filename.

Rule of thumb: one class per header and source file, in matching subdirectories, with matching names.

This avoids large, monolithic files that contain many unrelated classes and makes it much easier to locate the implementation of a given class.

Grouping Related Classes

Within your src and include directories, think in terms of functional groups, not in terms of the order in which you wrote the classes. The following grouping works well for many Geant4 applications.

Geometry and materials classes form one group. Your main detector construction class resides here, together with helper classes for complex detector components and any material factory classes that create custom materials.

Primary generation forms another small group. The primary generator action and any custom source description code, spectra loaders, or GPS helper classes can live together.

Physics configuration can be in its own group. If you write custom physics lists or modify reference lists, keep that code separate from the rest.

User actions such as run, event, stepping, and tracking actions are closely related and should be grouped. They all manage the flow of data through the simulation and often coordinate with analysis code.

Analysis code, such as classes that wrap G4AnalysisManager or manage histograms and ntuples, should be kept together and not mixed into geometry or primary generator code.

You can reflect these groups in subdirectories, for example:

SubdirectoryTypical contents
geometry/Detector construction and component classes
materials/Material definitions and helpers
primary/Primary generator actions and source helpers
physics/Physics list configuration classes
actions/Run, event, stepping, tracking, stacking actions
analysis/Histogram, ntuple, and output file management

Using this style, the path of a file already tells you its role and approximate dependencies.

Managing Dependencies Between Modules

Good organization is not only about where files sit, but also about how they depend on each other. Cyclic dependencies make code harder to compile, refactor, and test. In Geant4 applications, a clean dependency direction is especially useful.

A practical dependency order from low level to high level is:

  1. Utility and configuration code, which should not depend on any detector specific classes.
  2. Materials and simple geometry helpers.
  3. Detector components and overall detector construction.
  4. Physics list configuration.
  5. Primary generator description.
  6. User actions that observe and record what happens.
  7. Analysis and output.

Your main program sits on top and knows about all these modules. The lower a module is in this list, the fewer project specific things it should know. Ideally, geometry should not know about analysis. Geometry does not need to fill histograms, it only needs to define where things are. Analysis code can query energy deposit in volumes, using sensitive detectors and hits, without adding dependencies back into geometry.

Avoid tying geometry, physics, and primary generation directly to analysis output. Let analysis observe, not control, the rest of the simulation.

This makes it far easier to reuse geometry in another project, or to switch from one analysis backend to another, without touching the core of your simulation.

Splitting Geometry into Reusable Components

Detector constructions become complex very quickly. If you put everything into a single DetectorConstruction class, it will soon be long and hard to modify. A more scalable method is to treat complex detectors as compositions of smaller parts. Each part can be a class that creates its own logical volumes and placements and then returns a handle to the top logical volume of that component.

For example, a scintillation detector setup can be built from separate classes that describe the scintillator crystal, light guides, photodetectors, and shielding. The main detector construction then creates the world and instantiates each component, placing them with G4PVPlacement calls. Each component class focuses on a single part of the geometry, and you can test and modify it without scrolling through the entire detector description.

By placing each component in its own header and source files and grouping them under a directory such as geometry/ or detectors/, you gain a library of reusable parts. Another simulation can reuse a detector module by including its header and linking the corresponding source file, without copying large blocks of code.

Organizing User Actions and Analysis Hooks

User action classes are the natural place to access simulation results and pass them to analysis. To keep code organization clean, let each action focus on its own level. For example, the event action can collect event level quantities, but should not handle file opening and closing. Run action is better suited for that.

You can design a small analysis manager class of your own that wraps G4AnalysisManager calls. This manager can expose simple methods such as FillEnergySpectrum or RecordHit, and hide details of histogram IDs and column indices. User actions then depend only on your analysis manager interface, not directly on how histograms are organized.

Keep analysis classes under a dedicated analysis/ directory, and pass pointers or references to these classes into your action initialization. This keeps initialization code in one place and avoids scattering file names and histogram definitions throughout the application.

This separation makes it easy to change the analysis output format. If you later decide to switch from ROOT to CSV output, most changes occur inside analysis classes, not in geometry or primary generator code.

Configuration and Parameters in a Structured Project

Once your project has a clear structure, you can also think clearly about where configuration lives. Hard coding physical parameters into many classes makes it difficult to perform parameter studies or to maintain consistency. A more organized approach is to centralize key parameters and expose them through a configuration class or macro commands.

A configuration class can hold values such as detector dimensions, material choices, and source characteristics. Geometry and primary generator classes can query this configuration rather than using literal numbers. Place such configuration classes under a directory such as config/ or keep them close to the main program if they are simple.

You can also connect configuration to the Geant4 command system. For example, a messenger class can modify parameters in your configuration object in response to macro commands. This allows you to change aspects of your simulation through macro files without recompilation, while still keeping code structure clean and consistent.

Scaling Up: From Example to Framework

As you gain experience, your Geant4 project can evolve from a single application into a small framework for your group or for a particular class of detectors. The organization principles remain the same, but you start thinking of directories as modules that you could plug into more than one executable.

For example, you may keep a generic detector framework under one top level directory, and several applications under another. Each application then selects components, geometry modules, and analysis modules that it needs. This is easiest if you have already kept modules independent, with their own headers and source files, and with a clean dependency direction.

Even for beginners, adopting a consistent layout and grouping from the start makes this evolution smoother. You do not need to design a large framework in advance, you only need to avoid mixing unrelated responsibilities and to keep files small, focused, and easy to locate.

Views: 9

Comments

Please login to add a comment.

Don't have an account? Register now!