Add run_sampling executable for sampling calorimeter geometries
Mirrors run_pbwo4 but alternates thin absorber and active layers, with four selectable configs (by name or 1-based index): pb_scint, fe_scint, w_scint_ecal (homogeneous ECAL-like preshower + W/scint sampling section), and pb_lar. Each is sized to reach ~20-25 X0 of absorber despite the dilution from inactive gaps. Wired into the top-level CMakeLists, superbuild, and Dockerfile alongside run_pbwo4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,9 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
|||||||
add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers})
|
add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers})
|
||||||
target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
||||||
|
|
||||||
|
add_executable(run_sampling run_sampling.cc ${sources} ${headers})
|
||||||
|
target_link_libraries(run_sampling ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
||||||
|
|
||||||
#----------------------------------------------------------------------------
|
#----------------------------------------------------------------------------
|
||||||
# Install the executable to 'bin' directory under CMAKE_INSTALL_PREFIX
|
# Install the executable to 'bin' directory under CMAKE_INSTALL_PREFIX
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -26,14 +26,15 @@ A Geant4-based calorimeter simulator for teaching and research. Supports arbitra
|
|||||||
|
|
||||||
miniCaloSim wraps the standard Geant4 B4a example into a configurable geometry driven by a `GeometryDescriptor` object. A geometry is a stack of layers; each layer is a slab of named NIST material with an optional sensor grid that accumulates deposited energy. The primary particle type and energy are configurable at run time.
|
miniCaloSim wraps the standard Geant4 B4a example into a configurable geometry driven by a `GeometryDescriptor` object. A geometry is a stack of layers; each layer is a slab of named NIST material with an optional sensor grid that accumulates deposited energy. The primary particle type and energy are configurable at run time.
|
||||||
|
|
||||||
The default executable `run_pbwo4` simulates electron showers in a 20 cm PbWO₄ crystal with a 10×10 sensor grid and writes a ROOT file.
|
The default executable `run_pbwo4` simulates electron showers in a 20 cm PbWO₄ crystal with a 10×10 sensor grid and writes a ROOT file. A second executable, `run_sampling`, simulates the same kind of showers in a sampling calorimeter (alternating thin absorber and active layers); the layer configuration is selected by name at run time.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
```
|
```
|
||||||
run_pbwo4.cc main executable entry point
|
run_pbwo4.cc homogeneous PbWO4 crystal executable entry point
|
||||||
|
run_sampling.cc sampling calorimeter executable entry point (selectable layer configs)
|
||||||
src/ Geant4 application sources
|
src/ Geant4 application sources
|
||||||
ActionInitialization.cc
|
ActionInitialization.cc
|
||||||
DetectorConstruction.cc reads GeometryDescriptor, builds Geant4 volumes
|
DetectorConstruction.cc reads GeometryDescriptor, builds Geant4 volumes
|
||||||
@@ -69,22 +70,23 @@ Requires a Geant4 ≥ 11 installation visible to CMake (e.g. via `Geant4_DIR` or
|
|||||||
```bash
|
```bash
|
||||||
cmake -B build -S .
|
cmake -B build -S .
|
||||||
cmake --build build --parallel $(nproc)
|
cmake --build build --parallel $(nproc)
|
||||||
# executable: build/run_pbwo4
|
# executables: build/run_pbwo4, build/run_sampling
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option B — build Geant4 from submodule
|
### Option B — build Geant4 from submodule
|
||||||
|
|
||||||
The superbuild compiles Geant4 from `lib/geant4` into `build/geant4-install`, then builds minicalosim against it. Three targets are available:
|
The superbuild compiles Geant4 from `lib/geant4` into `build/geant4-install`, then builds minicalosim against it. Four targets are available:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cmake -B build -S superbuild/ # configure once
|
cmake -B build -S superbuild/ # configure once
|
||||||
|
|
||||||
cmake --build build --target geant4 # compile Geant4 (~30–60 min)
|
cmake --build build --target geant4 # compile Geant4 (~30–60 min)
|
||||||
cmake --build build --target run_pbwo4 # compile minicalosim
|
cmake --build build --target run_pbwo4 # compile run_pbwo4
|
||||||
cmake --build build # both
|
cmake --build build --target run_sampling # compile run_sampling
|
||||||
|
cmake --build build # all
|
||||||
```
|
```
|
||||||
|
|
||||||
After the build, the executable is at `build/run_pbwo4` (symlink in the superbuild case).
|
After the build, the executables are at `build/run_pbwo4` and `build/run_sampling` (symlinks in the superbuild case).
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
@@ -110,6 +112,30 @@ Output file name: `pbwo4_<nEvents>events_hits.root`.
|
|||||||
|
|
||||||
The geometry is hardcoded in `run_pbwo4.cc`: a single 20 cm PbWO₄ layer with a 10×10 sensor grid, bombarded with 1 GeV electrons.
|
The geometry is hardcoded in `run_pbwo4.cc`: a single 20 cm PbWO₄ layer with a 10×10 sensor grid, bombarded with 1 GeV electrons.
|
||||||
|
|
||||||
|
### Sampling calorimeter (`run_sampling`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# default config (pb_scint), 10 events, writes sampling_pb_scint_10events_hits.root
|
||||||
|
build/run_sampling
|
||||||
|
|
||||||
|
# pick a config by name, default event count
|
||||||
|
build/run_sampling fe_scint
|
||||||
|
|
||||||
|
# pick a config by 1-based index, and a number of events
|
||||||
|
build/run_sampling 3 500
|
||||||
|
```
|
||||||
|
|
||||||
|
The config can be given either by name or by its 1-based index (`run_sampling --help`-style usage, including the index list, is printed on an invalid selection). Output file name: `sampling_<configName>_<nEvents>events_hits.root` (always uses the resolved name, even when selected by index).
|
||||||
|
|
||||||
|
All configs use a 10×10 sensor grid per active layer and are sized to reach ~20–25 X₀ of absorber, comparable to `run_pbwo4`'s 20 cm PbWO₄ block. Because a sampling stack dilutes the absorber with inactive gaps, the lower the absorber's Z the physically deeper the stack has to be to reach the same number of radiation lengths — `fe_scint` is twice as deep as `pb_scint` for this reason. Configs, defined in `run_sampling.cc`:
|
||||||
|
|
||||||
|
| # | Config name | Layers | Total depth | ~X₀ | Notes |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 1 | `pb_scint` | 60× (0.2 cm `G4_Pb` + 0.3 cm scint) | 30 cm | 21 | classic fine-sampling EM calo |
|
||||||
|
| 2 | `fe_scint` | 40× (1.0 cm `G4_Fe` + 0.5 cm scint) | 60 cm | 23 | coarse sampling, Tile-cal-like |
|
||||||
|
| 3 | `w_scint_ecal` | 1× 2.0 cm homogeneous scint, then 75× (0.1 cm `G4_W` + 0.2 cm scint) | 24.5 cm | 21 | thin homogeneous ECAL-like preshower (~0.05 X₀, sees MIPs/shower-start only) followed by a compact W/scint HCAL-like sampling section |
|
||||||
|
| 4 | `pb_lar` | 70× (0.2 cm `G4_Pb` + 0.4 cm `G4_lAr`) | 42 cm | 25 | same absorber as `pb_scint`, liquid argon active medium |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ROOT output format
|
## ROOT output format
|
||||||
@@ -195,6 +221,7 @@ A few material choices available via NIST names:
|
|||||||
| `G4_BGO` | 7.13 g/cm³ | inorganic crystal, ~0.8 CHF/cm³ |
|
| `G4_BGO` | 7.13 g/cm³ | inorganic crystal, ~0.8 CHF/cm³ |
|
||||||
| `G4_LSO` | 7.4 g/cm³ | inorganic crystal, ~6 CHF/cm³ |
|
| `G4_LSO` | 7.4 g/cm³ | inorganic crystal, ~6 CHF/cm³ |
|
||||||
| `G4_LYSO` | 7.1 g/cm³ | inorganic crystal, ~30 CHF/cm³ |
|
| `G4_LYSO` | 7.1 g/cm³ | inorganic crystal, ~30 CHF/cm³ |
|
||||||
|
| `G4_lAr` | 1.40 g/cm³ | liquid argon, cryogenic (ATLAS EM calo-style) |
|
||||||
|
|
||||||
### Absorbers (passive layers)
|
### Absorbers (passive layers)
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ COPY include/ include/
|
|||||||
COPY bind/ bind/
|
COPY bind/ bind/
|
||||||
COPY lib/ lib/
|
COPY lib/ lib/
|
||||||
COPY run_pbwo4.cc .
|
COPY run_pbwo4.cc .
|
||||||
|
COPY run_sampling.cc .
|
||||||
|
|
||||||
# lib/geant4/.git is a submodule pointer to the host's .git/modules dir, which
|
# lib/geant4/.git is a submodule pointer to the host's .git/modules dir, which
|
||||||
# is not copied into the image. Replace it with a fresh repo so Geant4's cmake
|
# is not copied into the image. Replace it with a fresh repo so Geant4's cmake
|
||||||
|
|||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
#include "GeometryDescriptor.hh"
|
||||||
|
#include "G4System.hh"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
GeometryDescriptor buildPbScint() {
|
||||||
|
GeometryDescriptor gd;
|
||||||
|
for (int i = 0; i < 60; ++i) {
|
||||||
|
gd.addLayer(0.2, "G4_Pb", false);
|
||||||
|
gd.addLayer(0.3, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||||
|
}
|
||||||
|
return gd;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeometryDescriptor buildFeScint() {
|
||||||
|
GeometryDescriptor gd;
|
||||||
|
for (int i = 0; i < 40; ++i) {
|
||||||
|
gd.addLayer(1.0, "G4_Fe", false);
|
||||||
|
gd.addLayer(0.5, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||||
|
}
|
||||||
|
return gd;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeometryDescriptor buildWScintEcal() {
|
||||||
|
GeometryDescriptor gd;
|
||||||
|
// Thin homogeneous preshower: ~0.05 X0, sees MIPs/shower-start, not containment.
|
||||||
|
gd.addLayer(2.0, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||||
|
for (int i = 0; i < 75; ++i) {
|
||||||
|
gd.addLayer(0.1, "G4_W", false);
|
||||||
|
gd.addLayer(0.2, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||||
|
}
|
||||||
|
return gd;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeometryDescriptor buildPbLAr() {
|
||||||
|
GeometryDescriptor gd;
|
||||||
|
for (int i = 0; i < 70; ++i) {
|
||||||
|
gd.addLayer(0.2, "G4_Pb", false);
|
||||||
|
gd.addLayer(0.4, "G4_lAr", true, 10, 10);
|
||||||
|
}
|
||||||
|
return gd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordered (not alphabetical) so configs can also be selected by 1-based index.
|
||||||
|
const std::vector<std::pair<std::string, GeometryDescriptor (*)()>> kConfigs = {
|
||||||
|
{"pb_scint", buildPbScint},
|
||||||
|
{"fe_scint", buildFeScint},
|
||||||
|
{"w_scint_ecal", buildWScintEcal},
|
||||||
|
{"pb_lar", buildPbLAr},
|
||||||
|
};
|
||||||
|
|
||||||
|
void printUsage() {
|
||||||
|
std::cerr << "Usage: run_sampling [configName|configIndex] [nEvents]" << std::endl;
|
||||||
|
std::cerr << "Available configs:" << std::endl;
|
||||||
|
for (std::size_t i = 0; i < kConfigs.size(); ++i) {
|
||||||
|
std::cerr << " " << (i + 1) << ": " << kConfigs[i].first << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
std::string configName = kConfigs.front().first;
|
||||||
|
int nEvents = 10;
|
||||||
|
|
||||||
|
if (argc > 3) {
|
||||||
|
printUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (argc >= 2) {
|
||||||
|
configName = argv[1];
|
||||||
|
}
|
||||||
|
if (argc == 3) {
|
||||||
|
try {
|
||||||
|
nEvents = std::stoi(argv[2]);
|
||||||
|
if (nEvents <= 0) throw std::invalid_argument("must be positive");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Invalid nEvents '" << argv[2] << "': " << e.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GeometryDescriptor (*builder)() = nullptr;
|
||||||
|
try {
|
||||||
|
std::size_t pos = 0;
|
||||||
|
int index = std::stoi(configName, &pos);
|
||||||
|
if (pos == configName.size() && index >= 1 && index <= static_cast<int>(kConfigs.size())) {
|
||||||
|
configName = kConfigs[index - 1].first;
|
||||||
|
builder = kConfigs[index - 1].second;
|
||||||
|
}
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
// Not a valid index; fall through to name lookup below.
|
||||||
|
}
|
||||||
|
if (builder == nullptr) {
|
||||||
|
for (const auto& kv : kConfigs) {
|
||||||
|
if (kv.first == configName) {
|
||||||
|
builder = kv.second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (builder == nullptr) {
|
||||||
|
std::cerr << "Unknown config '" << configName << "'." << std::endl;
|
||||||
|
printUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeometryDescriptor gd = builder();
|
||||||
|
|
||||||
|
std::string outfile = "sampling_" + configName + "_" + std::to_string(nEvents) + "events_hits.root";
|
||||||
|
|
||||||
|
G4System g4;
|
||||||
|
g4.init(gd, -1);
|
||||||
|
g4.run_batch(nEvents, {"e-"}, 1.0, 1.0, outfile);
|
||||||
|
|
||||||
|
std::cout << "Saved " << nEvents << " events to " << outfile << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -48,3 +48,19 @@ ExternalProject_Add(run_pbwo4
|
|||||||
${CMAKE_BINARY_DIR}/run_pbwo4
|
${CMAKE_BINARY_DIR}/run_pbwo4
|
||||||
DEPENDS geant4
|
DEPENDS geant4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ── minicalosim (run_sampling) ─────────────────────────────────────────────
|
||||||
|
# Shares run_pbwo4's BINARY_DIR (already configured against G4_INSTALL) and
|
||||||
|
# depends on it so the two ExternalProjects don't reconfigure that dir concurrently.
|
||||||
|
ExternalProject_Add(run_sampling
|
||||||
|
SOURCE_DIR ${MINI_SRC}
|
||||||
|
BINARY_DIR ${CMAKE_BINARY_DIR}/minicalosim-build
|
||||||
|
CMAKE_ARGS
|
||||||
|
-DCMAKE_PREFIX_PATH=${G4_INSTALL}
|
||||||
|
-DWITH_GEANT4_UIVIS=OFF
|
||||||
|
BUILD_COMMAND cmake --build <BINARY_DIR> --target run_sampling --parallel ${NPROC}
|
||||||
|
INSTALL_COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||||
|
${CMAKE_BINARY_DIR}/minicalosim-build/run_sampling
|
||||||
|
${CMAKE_BINARY_DIR}/run_sampling
|
||||||
|
DEPENDS run_pbwo4
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user